简介

这一次总结的是 React-redux 的实现,可以参考一下 大佬的文章 。

首先要知道 redux 的基本使用:

  1. 创建一个 Store

    <!-- store -->
    function createStore (reducer) {let state = nullconst listeners = []const subscribe = (listener) => listeners.push(listener)const getState = () => stateconst dispatch = (action) => {state = reducer(state, action)listeners.forEach((listener) => listener())}dispatch({}) // 初始化 statereturn { getState, dispatch, subscribe }
    }<!-- reducer -->
    const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
    }<!-- 创建 store -->
    const store = createStore(themeReducer)
    复制代码

    Store 是保存数据的地方,整个应用只有一个,调用 CreateStore 函数并且传入一个 Reducer 来创建一个 Store,并且会返回新的 Store 对象。

  2. 获取当前的 State

    <!-- 调用 store.getState 获取当前的状态 -->
    const state = store.getState()
    复制代码

    StateStore 里面包含的数据对象,可以通过 Store.getState() 获取

  3. 通过 Dispatch 发送 Action 改变 State

    <!-- 调用 dispatch 发送 action -->
    store.dispatch({type: 'CHANGE_COLOR',themeColor: 'blue'
    })
    复制代码

    Action 就是 View 发出的通知,表示 View 要变化,其中 Type 是必须的,其余可以 自定义 。

    如果要写多个 Action 觉得麻烦,可以使用 Action Creator 函数来生产 Action

    function updateThemeColor (action) {type: action.type,themeColor: action.themeColor
    }store.dispatch( updateThemeColor({ type: 'CHANGE_COLOR', themeColor: 'blue' }) )
    复制代码
  4. ReducerStore 收到 Action 之后用来计算 State 并且返回新的 State,也就是说必须要有 Return

    <!-- reducer --><!-- 初始 state 是必须的,redux 规定不能为 undefined 和 null -->
    const themeReducer = (state = {}, action) => {switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
    }
    复制代码

    Reducer 可以根据不同的 Type 来进行不同的逻辑处理,并且每次都会返回新的 state 来覆盖原来的 state

    Reducer 是一个纯函数,同样的输入就会得到同样的输出。

    Reducer 必须要返回一个新的状态,而不是改变原有的状态,请参考下面写法:

    // State 是一个对象
    function reducer(state, action) {return Object.assign({}, state, { thingToChange });// 或者return { ...state, ...newState };
    }// State 是一个数组
    function reducer(state, action) {return [...state, newItem];
    }
    复制代码
  5. 调用 subscribe 传入一个函数,状态改变时会调用此函数。

    store.subscribe(()=> {ReactDOM.render()
    })
    复制代码

    Store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

    一般传入 renderthis.setState() 来监听页面重新渲染。

    调用此方法会返回一个函数,调用函数之后可以解除监听。


React-redux

首先之前向组件传递参数时,第一次使用的是 状态提升,即通过父级传入一个函数然后拿到组件里面的东西,再传入另一个组件里面。

当嵌套的太多层时,使用 状态提升 会非常麻烦,然后第二次就开始使用了 Context ,由于 Context 能随意被改变,这时我们可以把 ContextStore 结合使用,这样就不能随意的改变 Context ,并且状态还能共享。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';
<!-- store -->
function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});return { getState, dispatch, subscribe };
}
<!-- reducer -->
const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
}
<!-- 创建 store -->
const store = createStore(themeReducer);class Index extends Component {<!-- 设置子组件的 contextType -->static childContextTypes = {store: PropTypes.object}<!-- 设置 context -->getChildContext() {return { store }}render() {return (<div className="index"><Header /><Content /></div>)}
}ReactDOM.render(<Index />, document.getElementById('root'));
复制代码

创建 store 然后把它放到 context 里面,这样所有子组件都可以拿到了。

<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';class Header extends Component {static contextTypes = {store: PropTypes.object }constructor(props) {super(props);this.state = {themeColor: ''}}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h1 style={{color: this.state.themeColor}}>is header</h1></div>)}
}
export { Header };<!-- Content-->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><h2 style={{ color: this.state.themeColor }}>is Content</h2><ThemeSwitch /></div>)}
}
export { Content };<!-- ThemeSwitch -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';class ThemeSwitch extends Component {static contextTypes = {store: PropTypes.object}componentWillMount() {let { store } = this.context;this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}_updateThemeColor() {let state = this.context.store.getState();this.setState({themeColor: state.themeColor})}render() {return (<div className="header"><button style={{ color: this.state.themeColor }}>red</button><button style={{ color: this.state.themeColor }}>blue</button></div>)}
}export { ThemeSwitch };
复制代码

上面三个子组件使用 store.getState() 获取到 reducer 设置的默认状态,这样的话就可以实现共享状态了。

接下来我们实现点击按钮改变颜色:

<!-- ThemeSwitch -->
updateThemeColor(color) {let { store } = this.context;store.dispatch({type: 'CHANGE_COLOR',themeColor: color})
}render() {return (<div className="header"><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>)
}
复制代码

调用 dispatch 然后传入 action ,然后会调用 reducer 函数,然后根据传入的 action.type 改变状态,之后再返回一个新的状态。

返回新状态时要想监听页面的更新,可以在 subscribe 传入要监听的函数,这样就可以在调用 dispatch 同时会调用你传入的函数,然后再一次调用 this.setState 触发页面重新渲染。

  _updateThemeColor() {<!-- 重新获取一次状态 -->let state = this.context.store.getState();<!-- 重新设置,并且触发重新渲染 -->this.setState({themeColor: state.themeColor})}componentWillMount() {let { store } = this.context;<!-- 首次渲染 -->this._updateThemeColor();store.subscribe(() => this._updateThemeColor())}
复制代码

connect

上面的组件有着重复的逻辑,首先取出 storestate 然后设置成自己的状态,还有一个就是对 context 依赖过强,这时我们可以利用 高阶组件 来和 context 打交道,这时就不用每个组件都获取一遍 store 了。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
<!-- 接受一个组件 -->
const connect = (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render() {const { store } = this.context;return <WrappedComponent />}}return Connect;
}
export { connect };
复制代码

connect 是用于从 UI 组件生成 容器组件 ,也就是说我们传入的组件只是负责呈现和展示,而 容器组件 负责业务逻辑和带有内部状态,connect 负责的是将两者合并起来,生成并返回新的组件。

由于每个传进去的组件需要的 store 里面的数据都不一样,所以我们还要传入一个函数来告诉 高阶组件 正确获取数据。

  • mapStateToProps
const mapStateToProps = (state) {return {themeColor: state.themeColor}
}
复制代码

mapStateToProps 是一个获取 store 保存的状态,然后将这个状态转化为 UI组件 的数据的函数,它必须要返回一个对象,而这个对象用来进行状态转化的。

import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}render () {const { store } = this.context;<!-- 从store获取state并且转化之后全部传入 props -->let stateProps = mapStateToProps(store.getState());return <WrappedComponent {...stateProps} />}}return Connect;
}export { connect };
复制代码

上面最关键的一步就是调用 mapStateToProps 时,从 store 获取到 state 之后然后传入到 mapStateToProps 函数中,然后这函数会返回一个转化后的 state ,然后把这些转化的状态全部传入 props 里面。

可以看出 connectDumb组件(纯组件)context 连起来了,下面只需要调用 connect 然后传入一个 mapStateToPropsUI组件 就可以使用了。

<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class Header extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)}
} const mapStateToProps = (state) => {return {themeColor: state.themeColor}
}Header = connect(mapStateToProps)(Header)
export { Header };<!-- Content -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
import { ThemeSwitch } from './ThemeSwitch';class Content extends Component {static propTypes = {themeColor: PropTypes.string}render() {return (<div className="header"><h2 style={{ color: this.props.themeColor }}>is Content</h2><ThemeSwitch /></div>)}
}const mapStateToProps = (state) => {return {themeColor: state.themeColor}
}Content = connect(mapStateToProps)(Content)
export { Content };
复制代码

由于 mapStateToProps 返回的对象经过 connect 传入组件的 props 中,我们直接可以用 this.props 直接获取到。

接着把 connect 的代码复制到一个叫 React-redux 的文件,然后可以删掉之前那些引入 store 的代码了。

现在点击按钮只有按钮会变颜色,接下来我们修改一下 connect

import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.context<!-- 现在 mapStateToProps 可以接受两个参数 -->let stateProps = mapStateToProps(store.getState(), this.props)<!-- 整合普通的 props 和从 state 生成的 props -->this.setState({allProps: {...stateProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect;
}
export { connect };
复制代码

每次点击按钮调用 dispatch 都会把心的 state 设置到自己的 state 之后,然后返回给组件,这样组件之前的 props 也会保留,同时 mapStateToProps 可以接受第二个参数,这个参数为当前 UI组件props

<!-- 第一个为 store 获取到的 state , 第二个为当前 ui 组件的 props (不是最新的) -->
const mapStateToProps = (state, props) => {console.log(state, props)return {themeColor: state.themeColor}
}
复制代码

使用 props 作为参数后,如果容器组件的参数发生变化,也会引发 UI组件 重新渲染,connect 方法可以省略 mapStateToProps 参数,这样 store 的更新不会引起组件的更新。

  • mapDispatchToProps
const mapDispatchToProps = (dispatch, props) => {return {updateThemeColor: () => {dispatch({type: 'CHANGE_COLOR',payload: ''})}}
}
复制代码

mapDispatchToPropsconnect 的第二个参数,用来建立 UI组件 的参数到 store.dispatch 方法的映射,它作为函数时可以接受两个参数,一个是 dispatch ,一个则是 UI组件props

mapDispatchToProps 可以定义 action 然后传给 store

import React, { Component } from 'react';
import PropTypes from 'prop-types';const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends Component {static contextTypes = {store: PropTypes.object}constructor (props) {super(props);this.state = { allProps: {}}}componentWillMount () {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps())}_updateProps () {const { store } = this.contextlet stateProps = mapStateToProps? mapStateToProps(store.getState(), this.props): {}let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props): {}this.setState({allProps: {...stateProps,...dispatchProps,...this.props}})}render () {return <WrappedComponent { ...this.state.allProps } />}}return Connect;
}
export { connect };
复制代码

接受 mapDispatchToProps 作第二个参数,调用时把 dispatchprops 传进去,返回 onClickUpdate 然后直接传入 props 中返回给 UI组件 ,接着我们可以直接调用 this.props.onClickUpdate 然后调用 dispatch 来更新状态。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class ThemeSwitch extends Component {static contextTypes = {onClickUpdate: PropTypes.func}<!-- 点击调用 onClickUpdate -->updateThemeColor(color) {if(this.props.onClickUpdate) {this.props.onClickUpdate(color)}}render() {return (<div className="header">      <button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button><button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button></div>)}
}
<!-- 在真正的 react-redux 不一定是函数 -->
const mapStateToProps = (state, props) => {return {themeColor: state.themeColor}
}
<!-- 在真正的 react-redux 可以是一个对象 -->
const mapDispatchToProps = (dispatch, props) => {return {onClickUpdate: (color) => {dispatch({type: 'CHANGE_COLOR',themeColor: color})}}
}ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
export { ThemeSwitch };
复制代码

这样点击按钮之后又可以改变颜色了。

Provider

connect 方法生成容器后需要拿到 state 对象,目前咱们能拿到 store 是因为在 index.js 中设置了 context ,这样会直接污染 index.jsReact-redux 提供了 Provider 来充当最外层容器,这样就不需要在 index 设置 context 了。

class Provider extends Component {static propTypes = {store: PropTypes.object,children: PropTypes.any}static childContextTypes = {store: PropTypes.object}getChildContext () {return {store: this.props.store}}render () {return (<div>{this.props.children}</div>)}
}export { Provider };
复制代码

React-redux 的文件增加上面的代码,其实也就是另外设置一个容器来替代之前 index.js 干的活,这里返回了 this.props.children ,也说明要用这个组件把其他的组件包起来。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Provider } from './component/React-redux';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';function createStore(reducer) {let state = null;const listeners = [];const subscribe = (listener) => listeners.push(listener);const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach((listener) => listener());}dispatch({});return { getState, dispatch, subscribe };
}const themeReducer = (state, action) => {if (!state) return {themeColor: 'red'}switch (action.type) {case 'CHANGE_COLOR':return { ...state, themeColor: action.themeColor }default:return state}
}const store = createStore(themeReducer);class Index extends Component {render() {return (<div className="index"><Header /><Content /></div>)}
}ReactDOM.render(<!-- 把 store 和 外层组件包起来 --><Provider store= { store }><Index /></Provider>, document.getElementById('root')
);
复制代码

Store 传入给 Provider ,然后它把 store 设置成 context ,这样其他子组件都能拿到 store ,并且把最外层容器包起来,然后使用 this.props.children 全部罗列出来。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';class Header extends Component {<!-- 别忘了声明这玩意,不然拿不到 -->static contextTypes = {store: PropTypes.object}static propTypes = {themeColor: PropTypes.string}componentWillMount() {console.log(this.context)}render() {return (<div className="header"><h1 style={{color: this.props.themeColor}}>is header</h1></div>)}
} const mapStateToProps = (state, props) => {return {themeColor: state.themeColor}
}Header = connect(mapStateToProps)(Header)
export { Header };
复制代码

拿到之后接下来就可以浪了,可以在当前组件调用里面的方法,非常灵活。

总结

  1. 首先在 index.js 引入创建好的 store ,然后引入 Providerindex 包起来,并且给它传递 store
  2. 如果页面需要拿到状态直接调用 store.getState ,如果想监听函数调用 store.subscribe 传入函数。
  3. 如果想订阅 store 或者修改 state ,在当前组件引入 connect 接着传入 mapStateToPropsmapDispatchToProps 来呈现新的 UI组件
  4. dispatch 还可以拓展,真正的 react-redux 还可以使用中间件实现异步 action ,如需要从后台返回的状态来改变当前的 state 类似这种操作。
  5. 可以使用 combineReducers 管理多个 reducer,一个 store 管理N种状态。

上一篇 --- React个人入门总结《四》

下一篇 --- React个人入门总结《六》

React个人入门总结《五》相关推荐

  1. React.js入门笔记

    # React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...

  2. web前端高级React - React从入门到进阶之React条件渲染

    系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到进阶之JSX虚拟DOM渲染 ...

  3. web前端高级React - React从入门到进阶之初识React

    第一部分:React入门 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  4. web前端高级React - React从入门到进阶之高阶组件

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  5. web前端高级React - React从入门到进阶之组件的状态提升

    系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到进阶之JSX虚拟DOM渲染 ...

  6. web前端高级React - React从入门到进阶之组件的懒加载及上下文Context

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  7. web前端高级React - React从入门到进阶之Render Props

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  8. Python3快速入门(五)——Python3函数

    Python3快速入门(五)--Python3函数 一.函数定义 1.函数定义 Python 定义函数使用 def 关键字,一般格式如下: def function_name(parameter_li ...

  9. React学习:入门实例-学习笔记

    文章目录 React学习:入门实例-学习笔记 React的核心 需要引入三个库 什么是JSX react渲染三步骤 React学习:入门实例-学习笔记 React的核心 1.组件化:把整一个网页的拆分 ...

最新文章

  1. C#实现Web应用程序定时启动任务
  2. 无需VR外设,普林斯顿学霸用DeepHand解放你的双手
  3. 如何预约升级鸿蒙,超过66万人预约,华为亮出真正王牌旗舰,支持优先升级鸿蒙系统...
  4. [Hadoop in China 2011] 何鹏:Hadoop在海量网页搜索中应用分析
  5. 查看nginx php mysql apache编译安装参数
  6. php中abs,php中的abs函数怎么用
  7. 作业2-Python基础练习
  8. python之numpy基础_Python之Numpy操作基础
  9. 服务器安装系统提示加载驱动程序,解决安装win7的提示“加载驱动程序”的问题...
  10. 右键txt打开html,文件解压不了怎么办 右键菜单中选择解压文件
  11. FastDFS V6.06 阿里云集群安装配置双IP(踩坑)
  12. ObjectARX开发笔记(一)——分别使用AcEdInputPointFilter和AcEdInputPointMonitor实现光标提示功能
  13. 第一次投稿(Elsevier)爱斯维尔期刊经验(持续更新)
  14. css中创建主轴方向,flex-direction
  15. linux smit工具,AIX smit工具
  16. svg背景_SVG电影背景:安迪的房间,俯瞰酒店
  17. 关于Editable的学习
  18. 海康工业相机SDK+OpenCV实例(2):RawDataFormatConvert详解
  19. hp服务器性能下降,MS SQL Server2000 运行在HP刀片服务器上性能下降,如何解决
  20. 2345浏览器的2.4版本,在2013年1月19日,像小丑一样笑着!

热门文章

  1. C++ 用libcurl库进行http 网络通讯编程
  2. Python数据存储:pickle模块的使用讲解(测试代码)
  3. HDU-Largest Rectangle in a Histogram-1506 单调栈
  4. poj3253 Fence Repair(贪心+哈夫曼 经典)
  5. ASP.NET MVC4 部分视图
  6. HDU 4336 概率DP 状压
  7. 读书笔记_代码大全_第14章_组织直线型代码_第15章_使用条件语句
  8. 多Kinect下WaitNoneUpdateAll老是报错,烦躁……
  9. rabbitmq 学习-2-安装
  10. 【android】读取/res/raw目录下的文件