• redux和react-redux的区别及用法


    笔记,参考程墨老师的《深入浅出React和Redux》。阅读之前可以先了解flux框架

    Redux框架

    image.png

    Redux原则

    Flux的基本原则是“单向数据流”, Redux在此基础上强调三个基本原则:

    • 唯一数据源

      唯一数据源指的是应用的状态数据应该只存储在唯一的一个Store上。这个唯一Store上的状态,是一个树形的对象,每个组件往往只是用树形对象上一部分的数据,而如何设计Store上状态的结构,就是Redux应用的核心问题

    • 保持状态只读

      保持状态只读,就是说不能去直接修改状态,要修改Store的状态,必须要通过派发一个action对象完成。改变状态的方法不是去修改状态上值,而是创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装。

    • 数据改变只能通过纯函数完成

      这里所说的纯函数就是Reducer, Redux这个名字的前三个字母Red代表的就是Reducer。按照创作者Dan Abramov的说法,Redux名字的含义是Reducer+Flux

      reducer(state,action)
      

      第一个参数state是当前的状态,第二个参数action是接收到的action对象,而reducer函数要做的事情,就是根据state和action的值产生一个新的对象返回,注意reducer必须是纯函数,也就是说函数的返回结果必须完全由参数state和action决定,而且不产生任何副作用,也不能修改参数state和action对象

      function reducer (state,action)=>{
          const {counterCaption}=action
          switch(action.type){
              case ActionTypes.INCREMENT:
                  return{...state,[counterCaption]:state[counterCaption]+1}
              case ActionTypes.DECREMENT:
                  return{...state,[counterCaption]:state[counterCaption]-1}
              default:
              	return state
          }
      }
      

    Redux示例

    示例

    基本目录如下:

    image.png

    效果:

    示例源码




    安装

    npm i --save redux
    

    Action

    和Flux一样,Redux应用习惯上把action类型和action构造函数分成两个文件定义

    src/ActionTypes.js:

    export const INCREMENT='increment'
    export const DECREMENT='decrement'
    

    src/Actions.js

    import * as ActionTypes from './ActionTypes.js';
    
    export const increament=(counterCaption)=>{
        return {
            type:ActionTypes.INCREMENT,
            counterCaption:counterCaption
        }
    }
    export const decrement=(counterCaption)=>{
        return {
            type:ActionTypes.DECREMENT,
            counterCaption:counterCaption
        }
    }
    

    对比发现Redux中每个action构造函数都返回一个action对象。而Flux版本中action构造函数则直接调用Dispatcher的dispatch函数把action对象派发出去。即redux与flux的区别在于,在actions文件中,redux不负责派发

    //flux版本
    import * as AcitonTypes from './ActionTypes';
    import AppDispatcher from './AppDispatcher'
    
    export const increment = (counterCaption)=>{
     AppDispatcher.dispatch({
         type:AcitonTypes.INCREMENT,
         counterCaption:counterCaption
     })
    }
    export const decrement = (counterCaption)=>{
     AppDispatcher.dispatch({
         type:AcitonTypes.DECREMENT,
         counterCaption:counterCaption
     })
    }
    

    Store

    Redux库提供的createStore函数:第一个参数代表更新状态的reducer,第二个参数是状态的初始值,第三个参数可选,代表StoreEnhancer。状态通过getStore()获取

    src/Store.js

    import {createStore} from 'redux'
    import reducer from './Reducer.js'
    const initValues={
        'First':0,
        'Second':10,
        'Third':20
    }
    const store=createStore(reducer,initValues)
    export default store
    

    Reducer

    用于更新状态。任何通过dispatch派发的action都会通过这里来改变store状态

    src/Reducer.js

    import * as ActionTypes from './ActionTypes.js'
    export default (state,action)=>{
        const {counterCaption}=action
        switch(action.type){
            case ActionTypes.INCREMENT:
                return{...state,[counterCaption]:state[counterCaption]+1}
            case ActionTypes.DECREMENT:
                return{...state,[counterCaption]:state[counterCaption]-1}
            default:
            	return state
        }
    }
    

    对比发现:Redux比Flux多了一个参数state,而在Flux中没有state参数。即redux与flux的区别在于,Flux中的state是由Store管理的,而Redux中的state由Redux本身管理。

    //flux版本
    CounterStore.dispatchToken=AppDispatcher.register((action)=>{
     if(action.type===AcitonTypes.INCREMENT){
         counterValues[action.counterCaption]++
         CounterStore.emitChange()
     }else if(action.type===AcitonTypes.DECREMENT){
         counterValues[action.counterCaption]--
         CounterStore.emitChange()
     }
    })
    

    View

    src/views/ControlPanel.js

    import React from 'react'
    import Counter from './Counter'
    import Summary from './Summary'
    
    export default class ControlPanel extends React.Component{
        render(){
            return(
            	<div>
                	<Counter caption="First" />
                    <Counter caption="Second" />
                    <Counter caption="Third" />
                    <hr/>
                    <Summary/>
                </div>
            )
        }
    }
    

    src/views/Counter.js

    import React from 'react'
    //1. 引入store和action
    import store from '../Store.js'
    import * as Actions from '../Actions'
    
    export default class Counter extends React.Component{
        constructor(props) {
            super(props);
            this.onChange = this.onChange.bind(this);
            this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
            this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
            //3. 初始化状态
            this.state = this.getOwnState()
        }
    
        //2. 返回一个从store中获得的相对应的状态
        getOwnState(){
            return {value:store.getState()[this.props.caption]}
        }
        
        //4. 保持store上状态和this.state的同步,只要store改变就调用onChange
        //   subscribe和unsubscribe分别用于注册与注销监听
        componentDidMount(){
            store.subscribe(this.onChange)
        }
        componentWillUnmount(){
            store.unsubscribe(this.onChange)
        }   
        onChange(){
            this.setState(this.getOwnState())
        }
    
        //5.  改变store状态唯一的方法是派发action。调用store.dispatch函数派发action
        onClickIncrementButton(){
            store.dispatch(Actions.increment(this.props.caption))
        }
        onClickDecrementButton(){
            store.dispatch(Actions.decrement(this.props.caption))
        }
        
        render(){
            const {caption} =this.props
            const value=this.state.value
            return(
            	<div>
                	<input type="button" onClick={this.onClickIncrementButton} value="+"/>
                    <input type="button" onClick={this.onClickDecrementButton} value="-"/>
                    <span>{caption} count:{value}</span>
                </div>
            )
        }
    }
    

    src/views/Summary.js

    import React from 'react'
    import store from '../Store.js'
    
    export default class Summary extends React.Component{
        constructor(props){
            super(props)
            this.onUpdate = this.onUpdate.bind(this);
            this.state=this.getOwnState.bind(this);
        }
        
        //1.定义求和函数,返回对象
        getOwnState(){
            const state=store.getState();
            let sum=0
            for(const key in state){
                if(state.hasOwnProperty(key)){
                    sum+=state[key]
                }
            }
            return {sum:sum}
        }
        
        //2. 同步状态
        componentDidMount(){
            this.setState(this.getOwnState())
            store.subscribe(this.onUpdate)
        }
        componentWillUnmount(){
            store.unsubscribe(this.onUpdate)
        }    
        onUpdate() {
            this.setState(this.getOwnState())
        }
        
        render(){
            return(
            	<div>
                	total:{this.state.sum}
                </div>
            )
        }
    }
    

    总结

    • 定义action类型
    • 创建action构造函数
    • 用createStore创建store:包含更新状态reducer、初始状态、(StoreEnhancer可选)
    • 创建reducer:包含state和ation参数
    • view部分初始化状态:通过store.getStore()获取状态
    • 通过subscribe和unsubscribe同步状态
    • 通过store.dispatch派发action,从而改变store状态

    改进Redux(1)—容器组件和傻瓜组件

    分析上面的Redux例子中的Counter组件和Summary组件可以发现:在Redux框架下,一个React组件基本上就是要完成以下两个功能:

    • 和Redux Store打交道,读取Store的状态,用于初始化组件的状态,同时还要监听Store的状态改变;当Store状态发生变化时,需要更新组件状态,从而驱动组件重新渲染;当需要更新Store状态时,就要派发action对象;
    • 根据当前props和state,渲染出用户界面

    如果发现一个组件做的事情太多了,就可以把这个组件拆分成多个组件,让每个组件依然只专注做一件事:

    • 负责和Redux Store打交道的组件,处于外层,被称为容器组件(Container Component)
    • 负责渲染界面的组件,处于内层,叫做展示组件(PresentationalComponent)

    image.png

    现在改装一下Counter组件:

    import React from 'react'
    import store from '../Store.js'
    import * as Actions from '../Actions'
    
    //1.容器组件
    class CounterContainer extends React.Component{
        constructor(props) {
            super(props);
            this.onChange = this.onChange.bind(this);
            this.onIncremnet = this.onIncremnet.bind(this);
            this.onDecrement = this.onDecrement.bind(this);
            this.state = this.getOwnState()
        }
        getOwnState(){
            return {value:store.getState()[this.props.caption]}
        }
        
        //同步状态
        componentDidMount(){store.subscribe(this.onChange)}
        componentWillUnmount(){store.unsubscribe(this.onChange)}   
        onChange(){this.setState(this.getOwnState())}
        
        //派发action
        onIncremnet(){
            store.dispatch(Actions.increment(this.props.caption))
        }
        onDecrement(){
            store.dispatch(Actions.decrement(this.props.caption))
        }
        
        render(){
            return <Counter caption={this.props.caption}
            onIncremnet={this.onIncremnet}
            onDecrement={this.onDecrement}
            value={this.state.value}
            />
        }
    }
    //默认导出容器组件
    export default CounterContainer
    
        
        
    //2.展示组件:写法一
    function Counter(props){
        const {caption,onIncremnet,onDecrement,value}=props
        return(
            <div>
                <input type="button" onClick={onIncremnet} value="+"/>
                <input type="button" onClick={onDecrement} value="-"/>
                <span>{caption} count:{value}</span>
            </div>
        )
    }
        
    //2.展示组件:写法二
    // function Counter({caption,onIncremnet,onDecrement,value}){
    //     return(
    //         <div>
    //             <input type="button" onClick={onIncremnet} value="+"/>
    //             <input type="button" onClick={onDecrement} value="-"/>
    //             <span>{caption} count:{value}</span>
    //         </div>
    //     )
    // }
    

    示例源码

    改进Redux(2)—组件context

    在Counter和Summary组件文件中,都导入了store。然而在组件中直接导入Store是非常不利于组件复用的。一个应用中,最好只有一个地方需要直接导入Store,这个位置应该是在调用最顶层React组件的位置。在该例子中,这个位置就是应用的入口文件src/index. js

    不让组件直接导入Store,那就只能让组件的上层组件把Store传递下来了。然而props存在缺陷,即所有的组件都要帮助传递这个props。React提供了一个叫Context的功能,能够完美地解决这个问题。

    image.png

    src/Provider.js

    import React from 'react'
    class Provider extends React.Component{
        getChildContext(){
            return{
                store:this.props.store
            }
        }
        render(){
            return this.props.children
        }
    }
    

    Provider提供函数getChildContext,这个函数了代表Context的对象。为了让Provider能够被React认可为一个Context的提供者,还需要指定Provider的childContextTypes属性。Provider定义类的childContextTypes,必须和getChildContext对应,这样这样,Provider的子组件才能访问到context。

    import PropTypes from 'prop-types';
    Provider.childContextTypes={
        store:PropTypes.object
    }
    

    src/index.js

    import store from './Store'
    import Provider from './Provider'
    
    ReactDOM.render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    );
    

    src/views/Counter.js

    • 给CounterContainer类的contextTypes赋值,应与Provider.childContextTypes的值一致,否则无法访问到context

      import PropTypes from 'prop-types';
      CounterContainer.contextTypes = {
          store: PropTypes.object
      }
      
    • 所有对store的访问,都是通过this.context.store完成。(同理src/views/Summary.js也是如此)

      componentDidMount(){this.context.store.subscribe(this.onChange)}
      
    • 由于定义了构造函数,所以要用上第二个参数context

      constructor(props,context) {
          super(props,context);
          ...
      }
      

      或者

      constructor() {
          super(...arguments);
          ...
      }
      

      示例源码

    改进Redux(3)—React-Redux

    上面的两个改进Redux应用的方法:第一是把一个组件拆分为容器组件和傻瓜组件,第二是使用React的Context来提供一个所有组件都可以直接访问的Context。实际上,已经有这样的一个库来完成这些工作了,这个库就是react-redux

    npm install --save react-redux
    

    react-redux的两个最主要功能:

    • Provider:提供包含store的context

    • connect:连接容器组件和傻瓜组件;


    Provider

    在src/index.js中,react-redux可以让我们不再使用自己实现的Provider,而是从它里面直接导入:

    import {Provider} from 'react-redux'
    

    connect

    connect让我们不再需要定义CounterContainer这样的容器组件,现在只需导出了一个这样的语句:

    import {connect} from 'react-redux';
    ...
    export default connect(mapStateToProps,mapDispatchToProps)(Counter)
    

    connect接收两个参数mapStateToProps和mapDispatchToProps。第一次是connect函数的执行,第二次是把connect函数返回的函数再次执行(参数为傻瓜组件Counter),最后产生的就是容器组件。connect函数做了以下两件事:

    • 把Store上的状态转化为内层傻瓜组件的prop

      //mapStateToProps函数:
      function mapStateToProps(state,ownProps){
          return {
              value:state[ownProps.caption]
              //ownProps是prop对象。ownProps.caption表示读取prop对象中的caption属性值
              //state[ownProps.caption]相当于获取caption属性值所对应store值
              //比如<Counter caption="First" />获取的就是store中First的属性值
              //然后傻瓜组件就可以通过props.value获取对应的值
          }
      }
      
    • 把内层傻瓜组件中的用户动作转化为派送给Store的动作

      //mapDispatchToProps函数
      function mapDispatchToProps(dispatch,ownProps){
          return{
              onIncrement:()=>{
                  dispatch(Actions.increment(ownProps.caption))
              },
              onDecrement:()=>{
                  dispatch(Actions.decrement(ownProps.caption))
              }
          }
      }
      

    与前面两个改进方法相比,Counter组件不用写同步状态部分


    完整代码:

    //Counter.js
    import * as Actions from '../Actions'
    import {connect} from 'react-redux';
    
    
    //把Store上的状态转化为内层傻瓜组件的prop
    function mapStateToProps(state,ownProps){
        return {
            value:state[ownProps.caption]
        }
    }
    
    //把内层傻瓜组件中的用户动作转化为派送给Store的动作
    function mapDispatchToProps(dispatch,ownProps){
        return{
            onIncrement:()=>{
                dispatch(Actions.increment(ownProps.caption))
            },
            onDecrement:()=>{
                dispatch(Actions.decrement(ownProps.caption))
            }
        }
    }
    //导出容器组件
    export default connect(mapStateToProps,mapDispatchToProps)(Counter)
    
    //傻瓜组件
    function Counter(props){
        const {caption,onIncrement,onDecrement,value}=props
        return(
            <div>
                <input type="button" onClick={onIncrement} value="+"/>
                <input type="button" onClick={onDecrement} value="-"/>
                <span>{caption} count:{value}</span>
            </div>
        )
    }
    
    //Summary.js
    import {connect} from 'react-redux'
    
    //把Store上的状态转化为内层傻瓜组件的prop
    function mapStateToProps(state) {
        let sum = 0;
        for (const key in state) {
          if (state.hasOwnProperty(key)) {
            sum += state[key];
          }
        }
        return {value: sum};
    }
    
    function Summary({value}) {
        return (
          <div>Total Count: {value}</div>
        );
    }
    
    export default connect(mapStateToProps)(Summary);
    
    //index.js
    import store from './Store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    );
    

    示例源码

    总结

    • 定义action类型

    • 创建action构造函数导出对象

    • 用createStore创建store:包含更新状态reducer、初始状态、(StoreEnhancer可选)

    • 创建reducer:包含state和ation参数

    • 从react-redux导入Provider组件,提供context,使在组件内的各个地方在不用导入store的情况下都能访问store

    • 从react-redux导入connect方法,接受两个函数,再接收一个傻瓜组件,最后生成一个容器组件。其中一个函数负责把Store上的状态转化为傻瓜组件的prop;另一个函数负责把傻瓜组件中的用户动作转化为派送给Store的动作

  • 相关阅读:
    JavaScript OOP 思想
    单页界面和 AJAX 模式
    jQuery 的 ready 函数是如何工作的?
    Dojo系列教程
    谈谈javascript语法里一些难点问题(一)
    2014年总结、2015年的小计划--女生程序员小感想
    Android名词解释
    【JS】defer / async
    关于对defer的理解.
    defer和async的区别
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13773556.html
Copyright © 2020-2023  润新知