经过 Redux架构理解 咱们了解到 Redux
架构的 store
、action
、reducers
这些基本概念和工做流程。咱们也知道了 Redux
这种架构模式能够和其余的前端库组合使用,而 React-redux
正是把 Redux
这种架构模式和 React.js
结合起来的一个库。前端
在 React
应用中,数据是经过 props
属性自上而下进行传递的。若是咱们应用中的有不少组件须要共用同一个数据状态,能够经过状态提高的思路,将共同状态提高到它们的公共父组件上面。可是咱们知道这样作是很是繁琐的,并且代码也是难以维护的。这时会考虑使用 Context
,Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。也就是说在一个组件若是设置了 context
,那么它的子组件均可以直接访问到里面的内容,而不用经过中间组件逐级传递,就像一个全局变量同样。react
在 App
-> Toolbar
-> ThemedButton
使用 props
属性传递 theme
,Toolbar
做为中间组件将 theme
从 App
组件 传递给 ThemedButton
组件。redux
class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // Toolbar 组件接受一个额外的“theme”属性,而后传递给 ThemedButton 组件。 // 若是应用中每个单独的按钮都须要知道 theme 的值,这会是件很麻烦的事, // 由于必须将这个值层层传递全部组件。 return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } }
使用 context
,就能够避免经过中间元素传递 props
了segmentfault
// Context 可让咱们无须明确地传遍每个组件,就能将值深刻传递进组件树。 // 为当前的 theme 建立一个 context(“light”为默认值)。 const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // 使用一个 Provider 来将当前的 theme 传递给如下的组件树。 // 不管多深,任何组件都能读取这个值。 // 在这个例子中,咱们将 “dark” 做为当前的值传递下去。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 中间的组件不再必指明往下传递 theme 了。 function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // 指定 contextType 读取当前的 theme context。 // React 会往上找到最近的 theme Provider,而后使用它的值。 // 在这个例子中,当前的 theme 值为 “dark”。 static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
虽然解决了状态传递的问题却引入了 2 个新的问题。性能优化
context
就像全局变量同样,里面的数据能够被子组件随意更改,可能会致使程序不可预测的运行。context
极大地加强了组件之间的耦合性,使得组件的复用性变差,好比 ThemedButton
组件由于依赖了 context
的数据致使复用性变差。咱们知道,redux
不正是提供了管理共享状态的能力嘛,咱们只要经过 redux
来管理 context
就能够啦,第一个问题就能够解决了。微信
React-Redux
提供 Provider
组件,利用了 react
的 context
特性,将 store
放在了 context
里面,使得该组件下面的全部组件都能直接访问到 store
。大体实现以下:架构
class Provider extends Component { // getChildContext 这个方法就是设置 context 的过程,它返回的对象就是 context,全部的子组件均可以访问到这个对象 getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
那么咱们能够这么使用,将 Provider
组件做为根组件将咱们的应用包裹起来,那么整个应用的组件均可以访问到里面的数据了app
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import todoApp from './reducers'; import App from './components/App'; const store = createStore(todoApp); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
还记得咱们的第二个问题吗?组件由于 context
的侵入而变得不可复用。React-Redux
为了解决这个问题,将全部组件分红两大类:展现组件和容器组件。dom
展现组件
展现组件有几个特征ide
UI
的展现,没有任何业务逻辑this.state
props
决定Redux
的 API
展现组件就和纯函数同样,返回结果只依赖于它的参数,而且在执行过程里面没有反作用,让人以为很是的靠谱,能够放心的使用。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; class Title extends Component { static propTypes = { title: PropTypes.string } render () { return ( <h1>{ this.props.title }</h1> ) } }
像这个 Title
组件就是一个展现组件,组件的结果彻底由外部传入的 title
属性决定。
容器组件
容器组件的特征则相反
UI
展现Redux state
获取Redux
的 API
你能够直接使用 store.subscribe()
来手写容器组件,可是不建议这么作,由于这样没法使用 React-redux
带来的性能优化。
React-redux
规定,全部的展现组件都由用户提供,容器组件则是由 React-Redux 的 connect()
自动生成。
React-redux
提供 connect
方法,能够将咱们定义的展现组件生成容器组件。connect
函数接受一个展现组件参数,最后会返回另外一个容器组件回来。因此 connect
实际上是一个高阶组件(高阶组件就是一个函数,传给它一个组件,它返回一个新的组件)。
import { connect } from 'react-redux'; import Header from '../components/Header'; export default connect()(Header);
上面代码中,Header
就是一个展现组件,通过 connect
处理后变成了容器组件,最后把它导出成模块。这个容器组件没有定义任何的业务逻辑,全部不能作任何事情。咱们能够经过 mapStateToProps
和 mapDispatchToProps
来定义咱们的业务逻辑。
import { connect } from 'react-redux'; import Title from '../components/Title'; const mapStateToProps = (state) => { return { title: state.title } } const mapDispatchToProps = (dispatch) => { return { onChangeColor: (color) => { dispatch({ type: 'CHANGE_COLOR', color }); } } } export default connect(mapStateToProps, mapDispatchToProps)(Title);
mapStateToProps
告诉 connect
咱们要取 state
里的 title
数据,最终 title
数据会以 props
的方式传入 Title
这个展现组件。
mapStateToProps
还会订阅 Store
,每当 state
更新的时候,就会自动执行,从新计算展现组件的参数,从而触发展现组件的从新渲染。
mapDispatchToProps
告诉 connect
咱们须要 dispatch action
,最终 onChangeColor
会以 props
回调函数的方式传入 Title
这个展现组件。
Connect
组件大概的实现以下
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) // 将 Store 的 state 和容器组件的 state 传入 mapStateToProps : {} // 判断 mapStateToProps 是否传入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) // 将 dispatch 方法和容器组件的 state 传入 mapDispatchToProps : {} // 判断 mapDispatchToProps 是否传入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { // 将 state.allProps 展开以容器组件的 props 传入 return <WrappedComponent {...this.state.allProps} /> } } return Connect }
至此,咱们就很清楚了,原来 React-redux
就是经过 Context
结合 Redux
来实现 React
应用的状态管理,经过 Connect
这个高阶组件来实现展现组件和容器组件的链接的。
更多精彩内容,欢迎关注微信公众号~