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 后界面上才会显示数据。

 

posted @ 2017-12-13 20:21  HelloHello233  阅读(256)  评论(0编辑  收藏  举报