react-redux
1. 首先redux,与react是两个独立的个体,项目中可以只用react,也可以只用redux
1.1 react-redux: 是一个redux作者专门为react制作的 redux, 增加了新的api,辅助react使用redux
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
2. UI组件
2.1 特征:
1 只负责 UI 的呈现,不带有任何业务逻辑 2 没有状态(即不使用this.state这个变量) 3 所有数据都由参数(this.props)提供 4 不使用任何 Redux 的 API
特征的意思就是:只展示数据,父亲传什么给他,他就展示什么,没有点击,拖动之类的事件逻辑,
注意,他并不是没有事件,只是把事件的要执行的函数放在容器组件里,
3.容器组件
3.1 容器组件与ui组件恰恰相反
1 负责管理数据和业务逻辑,不负责 UI 的呈现 2 带有内部状态 3 使用 Redux 的 API
意思就是:有事件响应,例如onClick后改变元素class
2 && 3 总结: 容器组价包裹UI组件,容器组件通过改变this.state, 改变子UI组件的this.props,从而达到改变ui
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
4.react-redux提供的方法
4.1 connect() : 为UI组件生成包裹它的容器组件
import { connect } from 'react-redux' const VisibleTodoList = connect()(TodoList);
4.1.1 为UI组件(TodoList)生成容器组件(VisibleTodoList),用于管理TodoList的数据,样式
4.1.2 管理之前,首先要告诉它怎么管理
//(1)输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数 //(2)输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。 import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList)
上面代码中,connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI 组件的业务逻辑。
前者负责输入逻辑,即将state
映射到 UI 组件的参数(props
),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
4.2 mapStateToProps()
4.2.0 若省略该参数,组件将不订阅store,不引发更新
4.2.1 会订阅 Store,每当state
更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
4.2.2 是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state
对象到(UI 组件的)props
对象的映射关系
4.2.3 执行之后返回一个对象,每一个key 对应着UI组件的 props
const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } //这类似于一个reducer const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) default: throw new Error('Unknown filter: ' + filter) } }
4.2.4 mapStateToProps
的第一个参数总是state
对象,还可以使用第二个参数,代表容器组件的props
对对象,而且只要组件接收到新的 props,mapStateToProps
也会被调用
// 容器组件的代码 // <FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } } //使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染
4.2.5 connect
方法可以省略mapStateToProps
参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
4.3 mapDispatchToProps()
4.3.0 若connect()省略该参数,dispatch会以props属性的形式传入ui组件
4.3.1 两种形式:函数,会得到dispatch
和ownProps
(容器组件的props
对象)两个参数,然后返回一个对象,每一个key对应着UI组件的事件,发出怎样的action
const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
4.3.2 两种形式: 对象 ,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }
两者的区别:1.函数可以获得ownProps,给每一个事件使用,对象必须在事件函数中加入自己的参数,
2.函数自己发出dispatch,对象由redux自动发出
5.<Provider> 组件
5.1 connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成 UI 组件的参数。
那么要怎样才能拿到state呢?
a: 将state
对象作为参数,传入容器组件(props)。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state
传下去就很麻烦。
b: React-Redux 提供Provider
组件,可以让容器组件拿到state;
import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
上面代码中,Provider
在根组件外面包了一层,这样一来,App
的所有子组件就默认都可以拿到state
了。
5.2 它的原理是React
组件的context
属性,请看源码。
5.2.1 Provider 设置context ,需要使用getChildContext,以及childContextTypes
class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
5.2.2 子组件获取context, 如下 context 已经注入每一个自组件,成为一个属性,this.context
class VisibleTodoList extends Component { componentDidMount() { const { store } = this.context; this.unsubscribe = store.subscribe(() => this.forceUpdate() ); } render() { const props = this.props; const { store } = this.context; const state = store.getState(); // ... } } VisibleTodoList.contextTypes = { store: React.PropTypes.object }
注意:以上context是 React-Redux
自动生成的容器组件的代码,就类似上面这样,从而拿到store
。
6.实例:
6.1 计数器
import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' import { createStore } from 'redux' import { Provider, connect } from 'react-redux' /*********************** 第一步:纯UI组件 ************************/ // React component class Counter extends Component { render() { const { value, onIncreaseClick } = this.props return ( <div> <span>{value}</span> <button onClick={onIncreaseClick}>Increase</button> </div> ) } } Counter.propTypes = { value: PropTypes.number.isRequired, onIncreaseClick: PropTypes.func.isRequired } /************* 第二步 定义mapStateToProps && mapDispatchToProps *******/ // Map Redux state to component props function mapStateToProps(state) { return { value: state.count } } // Map Redux actions to component props function mapDispatchToProps(dispatch) { return { onIncreaseClick: () => dispatch(increaseAction) } } // Action const increaseAction = { type: 'increase' } /************第三步:调用connect ****************/ // Connected Component const App = connect( mapStateToProps, mapDispatchToProps )(Counter) /* ************ 第四步: 定义组件的reducer ****************/ // Reducer function counter(state = { count: 0 }, action) { const count = state.count switch (action.type) { case 'increase': return { count: count + 1 } default: return state } } /* ************* 最后: 生成store 并使用Provider 包裹 ***********/ // Store const store = createStore(counter) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )