redux

Redux介绍

 

单一数据源

整个单页应用的 state 都被储存在store的内部,可以通过store.getState()获取,再作为props传给对应的组件。

state应该尽量少嵌套扁平化,通过id相互引用数据。

import { createStore } from 'redux';
const store = createStore(reducer);           //stroe需要reducer计算新的state

//createStore函数的简单实现
const createStore = (reducer) => {
  let state;
  let listeners = [];
  //返回store中的state tree的API
  const getState = () => state;
  //store.dispatch()会自动调用reducer生成新的state,并执行注册的回调函数
  const dispatch = (action) => {   
    //调用reducer更新state
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  //注册回调函数,通常是render或setState
  const subscribe = (listener) => {
    listeners.push(listener);
return () => {
    //注销回调函数
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});
  return { getState, dispatch, subscribe };
}; 

State 为只读

为防止出现race condition和deep equal的遍历,state应为只读,不能直接修改state,每个state都是新对象,而改变 state 的方法只有dispatch action。

使用纯函数reducer来修改state

reducer 是纯函数,接收prestate 和 action,返回新的 state。reducer必须保证同样的参数输入其输出一样,所以redux规定在Reducer函数里面不能直接改变 State,必须返回新的对象。在reducer中,不能改写参数,不能调用系统 I/O 的API,不能调用Date.now()或者Math.random()等不纯的方法,只是单纯的执行计算。例如 API 调用或路由跳转等有副作用的操作应该在 dispatch action 前发生。

function reducer(state, action) {
   switch (type) {
    case ADD_CHAT:
            //若state 是一个对象,不能直接修改state,而应使用如下方式返回新建的state
      return Object.assign({}, state, { newState });   
          // 或者return  { ...state, ...newState };
    default: return state;      //如果action不属于该reducer负责,需要返回原state
  }
}

// 若State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
   //或者
   return […state].concat(newItem);
}        

也可以使用immutable.js把 State 对象设成只读,这样改变State时只能通过生成一个新对象。

因为reducer函数的作用只是接收一个state计算后返回一个新的state,所以在应用中可以将多个分布在不同目录的reducer(函数)合成一个rootReducer,每个小的reducer只负责管理全局store中对应的一部分state:

export default function todoAppReducer(state = {}, action) {
  return {
    //只负责store中visibilityFilter和todos字段的state,别的state不可访问
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todosReducer(state.todos, action)                  //state.todos是大state中todosReducer负责的state
  }
}

//等价于

import { combineReducers } from 'redux';
const todoAppReducer = combineReducers({
  visibilityFilter,                //store的state树中的节点名和reducer函数名相同
  todos:todosReducer             //      设置不同的对应名字
})
export default todoApp;
//combineReducer将子reducer合成一个大reducer。每个 reducer 根据 key 来筛选出 state 中的一部分数据并处理。最后这个生成的函数再将所有 reducer 的结果合并成一个大的对象,从而更新store。
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {}
    );
  };
};

  array.reduce(function(accumulator, currentValue, currentIndex, array), initialValue)

  注意:若无initialValue,reduce 会跳过索引0从索引1开始执行 callback,若提供 initialValue,则从索引0开始。

如果需要在一个 Reducer 中访问另外一个 Reducer 负责的 state,这时需要自己写root Reducer或用reduce-reducers来控制。

state可以嵌套,对应的reducer亦可以嵌套,最终由combineReducer方法生成finalReducers,对应的state结构和reducer的嵌套结构相同。

 

action 是一个用于抽象描述已发生事件的普通对象,可以被序列化,是 store 数据的唯一来源。应尽量减少在 action 中传递的数据。

action Creator

View要发送多少种消息,就会有多少种 Action。可以定义一个Action Creato函数来生成 Action,不用每次都写样板代码。

const ADD_TODO = '添加 TODO';
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
store.dispatch( addTodo('Learn Redux') );      //store会自动调用reducer得到新的state
store.dispatch( addTodo('Learn React) ); 

可以直接通过 store.dispatch() 调用 dispatch() 方法,也可以用 react-redux 包提供的 connect()方法调用,更方便。

@connect() es7的类修饰器

React-Redux

Redux 和 React 之间没有直接关系,可以在react中直接使用store的方法,也可以通过react-redux模块进行绑定。

 

容器组件

展示组件

Location

最顶层,路由处理

中间和子组件,可复用

Aware of Redux

读取数据

从 Redux 获取 state

从 props 获取数据

修改数据

向 Redux 派发 actions

从 props 调用回调函数

 

 

 

 

 

 

 

 

容器组件Container Components:使用redux的API进行数据管理,带有内部状态。

展示组件Presentational Components:不使用任何 Redux 的 API,通过props传递数据,负责UI呈现,没有状态(不使用this.state这个变量)。

connect方法

从 UI 组件生成容器组件(UI component => container container),将react (ui)和redux (store)绑定起来,可以在嵌套的不同层次使用。

/*container component
*容器组件负责管理数据和业务逻辑,UI组件只显示视图
*/
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

//mapStateToProps将state映射到UI组件的参数(props),返回一个对象,其属性代表 UI 组件的同名参数。
//mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,最后触发 UI 组件的重新渲染。如果不传入mapStateToProps参数,store的更新不会引起UI组件的更新。
const mapStateToProps = (state, ownProps) =>{
  //state指reduce负责的那部分state,ownProps指容器父组件传入的所有属性
  //active是传给子组件的prop属性,父组件传入的其他props也会在子组件中
  return{
    active: ownProps.filter === state.visibilityFilter
  }
}

//如果mapDispatchToProps为函数,则应返回一个对象,其每个键值对都是一个映射,定义了用户对 UI 组件的操作怎样分发出 Action。
//若mapDispatchToProps是一个对象,则属性名对应UI组件的同名参数,属性值是一个当作Action creator的函数,其返回的action会由Redux自动发出给store。
const mapDispatchToProps = (dispatch, ownProps) => ({
    //onClick是传给子组件调用的方法,在view触发action时调用
  onClick: () => {
    dispatch(setVisibilityFilter(ownProps.filter))
  }
})

//如果不传入mapStateToProps/mapStateToProps,将默认只传入dispatch和父组件传入的props,不传入store
const FilterLink = connect(     // FilterLink是容器组件
  mapStateToProps,          // 将全局state对象映射到 UI 组件
  mapDispatchToProps        // UI 组件将传出action对象的方法
)(Link)             //Link为UI组件

export default FilterLink

 

/*UI 组件,只负责UI的呈现*/
import React from 'react'

//active和onClick是父容器组件通过connect方法传入的props属性
const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }
  return (
    <a href="#"
       onClick={e => {
         e.preventDefault()
         onClick()
       }}
    >
      {children}
    </a>
  )
}

export default Link

<Provider> 组件

使用Provider组件包裹根组件,将store传入容器组件,并通过组件的context属性使嵌套的所有子组件都能获得根state而不用一级一级传递下去。

//在任意组件引用全局store,尽量不要直接操作store的实例
Link.contextTypes = {
  store: React.PropTypes.object
}

 

posted @ 2017-08-22 15:35  开发之路  阅读(1440)  评论(0编辑  收藏  举报