zoukankan      html  css  js  c++  java
  • Redux

    http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

    http://cn.redux.js.org/docs/api/applyMiddleware.html

    也参考《深入React技术栈》

    Redux是Flux的实现,或者说Redux参考了Flux的设计。Redux是一个包,提供了一些api让我们方便创建store、更新数据等,简化我们的操作。

    Flux解决了MVC的局限性,Redux也遵循Flux的这个特点,所以使用Redux的原则就是一个应用的数据都存放在单一的数据源Store中(太大的话可以通过combineReducers来进行拆分),这解决了MVC 多个model,数据流混乱的问题,而且单个数据源的话,方便持久化。

    在Flux中,要求store是只读的(只允许内部对store进行修改),也就是没有给外界提供setter方法,在Redux中,更加发扬这种只读的特性,修改状态都是在reducer中返回一个全新的state,而不会对原来的state进行修改

    Redux = Reduce + Flux。所以在Redux中对这两者的理解十分重要

    重申一下redux和flux在应用上的区别:

      redux:调用storeA.dispatch,storeA中的reducer会被触发执行,而storeB中的reducer不会执行。

      Flux:调用Dispatcher.dispatch方法,所有store在上面register的方法都会执行

    因为store中的state代表整个应用程序的状态,而修改state,必须通过dispatch(Action)来实现,也就是说每次改变对应一个action,只需要跟踪action,即可跟踪整个程序的状态变化

    Store

    import { createStore } from 'redux';
    const store = createStore(reducer);

    在View中调用dispatch发送action

    store.dispatch({
      type: 'ADD_TODO',
      payload: 'Learn Redux'
    });
    
    // 使用Action Creator
    store.dispatch(addTodo('Learn Redux'));

    查看 这里 ,store代表了整个应用程序的状态,而状态的初始化当然也是在store中进行处理,有两种方式:

    1. 指定reducer中的state参数默认值,store被创建的时候,这个reducer会被执行一次,reducer内我们把这个默认值返回返回即可
    2. 指定createStore的第二个参数,这个参数代表默认值,这种方式指定的优先级比以上默认值方式更高,即这个值会在初始化的时候被传递进reducer的state。

    以上两种方式的本质都是要求reducer返回一个初始值,只不过这个初始值可能是默认值而已。

    reducer

    这个函数的职责就是根据当前state以及action,计算出下一个新的状态new_state,如果不需要发生变化,则返回原state即可

    创建Store的时候,需要传入一个函数作为Store的Reducer。以上dispatch之后,这个Action就会被传入到这个Store中的reducer进行处理,返回一个新的state

    const reducer = function (state, action) {
      // ...
      return new_state;
    };

    对于程序的初始状态,可以这么进行设置(在执行createStore的时候,reducer会被首次执行用于获取初始状态,显然这个时候store没有任何状态,所以应该给state设置一个默认值,作为初始状态):

    const reducer = (state = defaultState, action) => {
      switch (action.type) {
        case 'ADD':
          return state + action.payload;
        default: 
          return state;
      }
    };

    规范要求Reducer是一个纯函数,不能对输入数据进行修改,而且相同的输入对应相同的输出(更加方便测试)。所以必须要返回一个全新的对象,手段如下:

    function reducer(state, action) {
      return Object.assign({}, state, { thingToChange });
      return { ...state, ...newState };
      return [...state, newItem];
    }

    以上reduce返回了一个不同的对象(state),就代表界面需要刷新,因为状态发生变化了。这需要在view中对store进行监听:

    store.subscribe(listener);

    在以上的listener中调用setState即可。

    回顾以上的逻辑(调用了Store的subscribe、getState和dispatch方法):

      在模块中创建store,view引入这个模块,进行store.subscrible注册监听。

      在注册的回调函数中,先调用store.getState获取到最新的状态,然后把数据通过setState更新到界面上(View模板中通过this.state.xx获取数据)

      用于与view发生交互,view调用store.dispatch,然后store中的reducer被执行,返回一个新的state,以上的监听就会被触发

    Reducer拆分

    以上可以发现一个store中对状态的修改全部集中在reduce函数中,有必要对这个函数进行拆分

    const chatReducer = (state = defaultState, action = {}) => {
      return {
        chatLog: chatLog(state.chatLog, action),
        statusMessage: statusMessage(state.statusMessage, action),
        userName: userName(state.userName, action)
      }
    };

    可见以上的state中有3个字段,每个字段的值都通过一个函数的返回值来决定,在这个函数中进行action.type的判断,再进行数据修改,返回最新的值。

    这个过程可以使用Redux提供的语法糖combineReducers进行更改(这里使用了对象的简洁写法):

    const chatReducer = combineReducers({
      chatLog,
      statusMessage,
      userName
    })

    Reducer的复用

      以上使用combineReducers的时候,可能会遇到一个问题,如chatLog与statusMessage中对数据的处理逻辑很类似,怎么把这两个函数中的逻辑抽取出来呢?

      假如这两部分直接使用相同的函数,这会造成一个问题,即这两个属性对于相同的action,返回的数据是一致的(因为调用的是同一个函数,里面对action.type的判断以及数据处理是完全一致的),即会同时相同改变。这不是我们想要的。

      这时可以使用一个函数(高阶reducer)来生成这些需要被复用的reducer,然后通过参数来控制这些reducer的差异,实现返回的reducer内部逻辑大部分相同,但存在差异。如为里面判断的action.type添加一个前缀

    State

    一个view和一个state相互对应

    const state = store.getState();

    Action

    用户与View发生交互,View发出Action,触发State的变化(因为State是从Store中获取的,所以也可以说成Store发生变化)

    const action = {
      type: 'ADD_TODO',
      payload: 'Learn Redux'
    };

    Action Creator简化Action的生成

    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    const action = addTodo('Learn Redux');

    中间件

    分析以上过程可以发现,Dispatch到reduce之间的细节被封装到Redux框架内部,假如我希望在执行dispatch的时候先进行一些操作,后执行reduce或者说要增强dispatch。这就需要用到中间件。

    使用Flux架构进行开发,会遇到一个问题,纠结在哪里处理异步,哪里发请求,这是因为Flux没有规定在哪里进行异步请求(感觉其实也可以这样,在action creator中调用dispatch之前进行异步请求,有了结果之后再进行dispatch action 告知结果)。而redux在这点上提供了解决方案,使用中间件增强dispatch,使其可以处理异步请求。

    手动实现一个日志中间件

    let next = store.dispatch;
    store.dispatch = function dispatchAndLog(action) {
      console.log('dispatching', action);
      next(action);
      console.log('next state', store.getState());
    }

    redux-thunk

    thunk返回一个接收 ‘ dispatch’ 的函数Fn,在Fn中进行异步操作,返回promise,在这个promise上注册的回调函数中可以调用dispatch,使用的方式如下:

    // thunk
    function makeASandwichWithSecretSauce(forPerson) {
      return function (dispatch,getState) {
        return fetchSecretSauce().then(
          sauce => dispatch(makeASandwich(forPerson, sauce)),
          error => dispatch(apologize('The Sandwich Shop', forPerson, error))
        )
      }
    }
    
    // Thunk middleware 可以让我们像 dispatch 普通 action
    // 一样 dispatch 异步的 thunk action。
    store.dispatch(
      makeASandwichWithSecretSauce('Me')
    )

    本质就是thunk返回的函数中返回一个promise,在这个promise的异步操作中调用dispatch,可以发送action。

    React-redux

    redux不一定要配合react来使用,更加通用。而react-redux提供了api帮助react和redux进行绑定

    了解容器型组件和展示型组件 。容器组件由React-Redux生成,即状态全部交由框架来管理。

    http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html

    基本的用法就是最外层一个provider,接着就是容器组件,这个容器组件是通过react-redux的connect生成的,connect为这个容器组件中的所有子组件提供了访问store的功能,connect帮助store与组件建立关联(state映射到props),即通过组件内的props可以获取到store中state的值,以及当state发生变化,组件会自动重新渲染(connect中mapStateToProps(state)和mapDispatchProps(dispatch)也会重新执行,即重新执行映射),而这些功能都在Container中做好了。

    这里记录一些使用要点:

      首先定义一个展示型组件(无状态组件),里面UI的回调函数以及要展示的数据都是通过props获取的
      通过connect根据展示型组件生成一个容器组件,还需要函数mapStateToProps(state)和mapDispatchProps(dispatch),前者用于把传入的state(第一次执行的话这个state就是来源于store的初始state,有两种方法可以指定),映射成props对象属性返回,这里用到的state是通过store.getState获取,而store后面会传入;后者也是用于定义props对象属性返回,这不过这些属性都是函数,用于被容器中的子组件调用,在这些函数中调用dispatch发送action,或者这些函数中直接返回一个action,也会被自动发送。

      以上创建完成容器组件之后,接着通过createStore(reducer)创建store,根据以往的理解,接下来需要通过store.subscrible进行监听,如果reducer返回的state发生变化,则回调函数会被执行,里面执行setState触发重新渲染。但这个监听的步骤在react-redux中已经做好了,在容器组件被实例化的时候,会获取外部的store(后面会说道怎么把store传给容器组件),然后进行监听,已经重新渲染等操作。

      最后通过provider的store属性(provider组件从React-redux中获取),把我们创建的store传递进去即可。被provider包着的容器组件内部就可获取到这个store了。回顾context的用法就知道provider内部做了什么(定义getChildContext)以及容器组件是如何获取到store了(通过this.context获取)。

      对于树形的组件结构如何用react-redux来实现?展示型组件中有多个子组件,这些子组件可以拥有自己的状态,也可以通过获取上级展示型组件中的props来展示数据。

     dispatch除了可以在中mapDispatchProps(dispatch)使用,还可以在被连接的展示型组件中使用:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore} from 'redux'
    import { Provider,connect } from 'react-redux'
    
    let Btn = ({dispatch})=>{
        return (
            <button onClick={()=>{dispatch({type:"click"})}}>click</button>
        )
    }
    
    const reducer = (state,action)=>{
        console.log(action)
        return state;
    }
    const store = createStore(reducer);
    Btn = connect()(Btn);
    
    ReactDOM.render(
        <Provider store={store}>
            <Btn />
        </Provider>,
        document.getElementById('root')
    );

    这里补充一个react-redux的异步状态初始化的例子:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore} from 'redux'
    import { Provider,connect } from 'react-redux'
    
    class Show extends React.Component{
        componentDidMount(){
            this.props.loadData();
        }
        render(){
            return <div>{this.props.msg}</div>
        }
    }
    const mapStateToProps = (state)=>{
        return {
            msg:state.msg
        }
    };
    const mapDispatchProps = (dispatch)=>{
        return {
            loadData:() => {
                setTimeout(()=>{
                    dispatch({type:"onLoaded",data:"async data"})
                },2000);
            }
        }
    }
    const Container = connect(mapStateToProps,mapDispatchProps)(Show);
    
    const reducer = (state, action) => {
        switch (action.type) {
            case "onLoaded":
                return {
                    msg: action.data
                }
        }
        return state
    }
    const initState = {msg:""}
    const store = createStore(reducer,initState);
    
    ReactDOM.render(
        <Provider store={store}>
            <Container/>
        </Provider>,
        document.getElementById('root')
    );

     运行结果:2s 后界面上才会显示数据。

     

  • 相关阅读:
    mklink让网盘同步不同文件夹
    WINDOWS 的 MKLINK : 硬链接,符号链接 : 文件符号链接, 目录符号链接 : 目录联接
    Dropbox Folder Sync – 让 Dropbox 同步任意文件夹
    Tasker : Scale Up/Down CPU Speed at Different Times
    Tasker : Task / Shortcut Widgets
    Automate Screen or Button Taps via Tasker : Simulating keypress events
    Solution for sending Whatsapp via sqlite "INSERT INTO"
    SQL Server复制出错文章集锦
    SQL Server 2016 CTP2.2 的关键特性
    SQL Server表分区的NULL值问题
  • 原文地址:https://www.cnblogs.com/hellohello/p/8034718.html
Copyright © 2011-2022 走看看