React笔记(八) React Redux

React笔记(八)

1.Redux
  • Redux是一个状态管理容器,提供一个可预测化的状态管理,在一般情况下不被使用。当某个组件的状态需要让其他组件随时可以拿到时,或一个组件需要改变另一个组件的状态时。才会使用,总体原则是,能不用就尽量不用。

  • 当父子组件间通信时,可以使用props进行值或方法的传递,当兄弟组件间通信时,可以使用状态提升。当两个陌生组件间通信时,还可以使用消息订阅与发布。消息订阅与发布可以理解为需要接收信息的组件为订阅方,传输信息的为发布方,当发布方发布消息时,订阅方会会收到消息。也就是一个订报与发报的关系。

    // 引入消息订阅与发布依赖,未安装则需要安装
    import PubSub from 'pubsub-js';
    
    export default function Demo(props) {
      return <>
      <ChildA/>
      <ChildB/>
      </>
    }
    
    function ChildA(){
      // 点击按钮后触发Puhsub的发布方法,进行消息发布
      return <button onClick={()=>PubSub.publish("publish_A","传值")}>我是组件A,点我传值</button>
    }
    
    function ChildB(){
      return <>
      <p>我是组件B</p>
      <GrandSonC/>
      </>
    }
    
    function GrandSonC(){
      const [data,setData] = React.useState('目前还未传值');
      React.useEffect(()=>{
        // 订阅消息,订阅到消息时触发状态更改,刷新页面
        // 这里订阅的第一个参数要与上方发布的第一个参数相同,并充当后面回调函数中的msg
        const token = PubSub.subscribe("publish_A",(msg,data)=>{
          console.log(msg);
          setData(data);
        })
        // 组件卸载时取消订阅
        return ()=>{PubSub.unsubscribe(token)}
      },[]);
      return <>
      <p>我是组件C,我接收到的订阅传值为:{data}</p>
      </>
    }
    
  • Redux相对于消息订阅与发布要复杂的多,但是能够实现的功能也多的多。如果需要陌生组件间共享的内容特别多时,这时消息订阅与发布便不再使用。此时使用Redux更好。

  • 在Redux中共有四个部分,也就是下图所示的四个部分,分别是表示状态的UI组件(React Components),传递想法的动作发生器(Action Creators),存储共享状态的仓库(Store),修改共享状态的工具(Reducers)。在这四个部分中,只有Store不存在复数,也就是说,除了此部分唯一以外,其余部分在项目中均可以存在多个。
    在这里插入图片描述

  • 这四部分的具体工作流程为:

    • 背景为A,B组件公用一套状态,A可以修改,B可以获取。(打电话点餐,A下单,B到店获取)
      1. A组件内部触发事件后,将想要对状态进行何种更改发送给动作发生器。(A下单订餐,给饭店打电话并告诉想要的菜品)
      2. 动作发生器接收到后创建一个动作并派送给状态仓库,告诉动作类型与数据。(饭店接话人根据客人的语言得知其想吃什么,并记录下来告诉老板)
      3. 状态仓库把接收到的内容,和当前的状态,递交给修改工具。(饭店老板看见后,查看库房的食物,并带着食物去找厨子)
      4. 修改工具根据接收到的动作,和当前的状态,创建一个新的状态并返回给状态仓库。(厨子根据原材料与菜单进行加工,把加工好的食物递给老板)
      5. 状态仓库将修改好的状态放置在仓库内,等待B组件获取。(饭店老板给B打电话,告诉餐好了,可以来取了,B去饭店取餐)

    具体的使用方法为

    import { createStore } from 'redux';
    
    // 定义常量,防止拼写错误
    const ADD = 'add';
    const SUB = 'sub';
    
    // 定义动作制作器,将接收到的消息处理后,派送给状态仓库
    function addActionCreator(num){
      return {type:ADD,data:num};
    }
    function subActionCreator(num){
      return {type:SUB,data:num};
    }
    
    // 定义默认状态
    const initState = 0;
    
    // 定义操作函数处理状态
    function DemoReducer(preState = initState, action){
      var state = preState;
      switch(action.type){
        case ADD:
          state+=parseInt(action.data);
          break;
        case SUB:
          state-=parseInt(action.data);
      }
      return state;
    }
    
    // 根据reducer,定义状态仓库
    const DemoStore = createStore(DemoReducer);
    
    export default function Demo(props) {
      return <>
      <CompA/>
      <CompB/>
      </>
    }
    
    function CompA(){
      const inputRef = React.useRef();
    
      return <>
      <input type="number" ref={inputRef} />
      {/* 点击按钮分发动作 */}
      <button onClick={()=>{DemoStore.dispatch(addActionCreator(inputRef.current.value))}}>点我数字加</button>
      <button onClick={()=>{DemoStore.dispatch(subActionCreator(inputRef.current.value))}}>点我数字减</button>
      </>
    }
    
    function CompB(){
      const initCount = DemoStore.getState();
      const [count,setCount] = useState(initCount);
      React.useEffect(()=>{
        // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
        const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState())});
        // 取消订阅
        return unSubScribe;
      },[]);
      return <p>{count}</p>
    }
    

    其中存在几个重要的API

    • createStore,接收一个reducer方法作为参数,用于制作Store。
  • subscribe,接受一个回调函数作为参数,用于创建订阅,在状态更改时调用回调函数并返回一个取消订阅的函数。

    • getState,获取当前Store内的状态。
  • 在Redux中存在三大原则:单一数据源,State是只读的,使用纯函数来执行修改

    • 单一数据源,代表着在一个React应用中,只存在一个Store树,所有的共享State放置在一起工程一个Store。这使得开发起来较为容易,并可以轻松实现"撤销/重做”等功能。
    • State是只读的,在实际操作时只允许通过触发action来修改state,不可以直接修改state。
    • 使用纯函数来执行修改,reducers必须是纯函数,接收到相同的参数时,返回的State必须相同。
  • 当存在有多个State时,这时可以使用combineReducers来进行操作,也就是对于Reducer的拆分。

    import { createStore,combineReducers } from 'redux';
    
    // 定义常量,防止拼写错误、
    // 多个Reducer时要注意不要使用相同的,因为在Dispatch时所有的Reducer都会参与运行
    const ADD = 'add';
    const SUB = 'sub';
    const ADD2 = 'add2';
    const SUB2 = 'sub2';
    
    // 定义动作制作器,将接收到的消息处理后,派送给状态仓库
    function addActionCreator(num){
      return {type:ADD,data:num};
    }
    function subActionCreator(num){
      return {type:SUB,data:num};
    }
    function add2ActionCreator(num){
      return {type:ADD2,data:num};
    }
    function sub2ActionCreator(num){
      return {type:SUB2,data:num};
    }
    
    // 定义默认状态
    const initState = 0;
    
    // 定义操作函数处理状态
    function DemoReducer(preState = initState, action){
      var state = preState;
      switch(action.type){
        case ADD:
          state+=parseInt(action.data);
          break;
        case SUB:
          state-=parseInt(action.data);
      }
      return state;
    }
    
    const initState2 = 0;
    function DemoReducer2(preState = initState2, action){
      var state = preState;
      switch(action.type){
        case ADD2:
          state+=parseInt(action.data);
          break;
        case SUB2:
          state-=parseInt(action.data);
      }
      return state;
    }
    
    
    // 结合reducer,并定义状态仓库
    const CombinedState = combineReducers({DemoReducer,DemoReducer2});
    const DemoStore = createStore(CombinedState);
    
    export default function Demo(props) {
      return <>
      <CompA/>
      <CompB/>
      <CompC/>
      </>
    }
    
    function CompA(){
      const inputRef = React.useRef();
    
      return <>
      <input type="number" ref={inputRef} />
      <button onClick={()=>{DemoStore.dispatch(addActionCreator(inputRef.current.value))}}>点我数字加</button>
      <button onClick={()=>{DemoStore.dispatch(subActionCreator(inputRef.current.value))}}>点我数字减</button>
      </>
    }
    
    function CompB(){
      // 获取State时方式改变要加上.Reducer进行获取
      const initCount = DemoStore.getState().DemoReducer;
      const [count,setCount] = useState(initCount);
      React.useEffect(()=>{
        // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
        const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState().DemoReducer)});
        // 取消订阅
        return unSubScribe;
      },[]);
      return <p>{count}</p>
    }
    
    function CompC(){
      console.log(DemoStore.getState());
      const initCount = DemoStore.getState().DemoReducer2;
      const [count,setCount] = useState(initCount);
      const inputRef = React.useRef();
    
      React.useEffect(()=>{
        // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
        const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState().DemoReducer2)});
        // 取消订阅
        return unSubScribe;
      },[]);
      return <><input type="number" ref={inputRef} />
      <button onClick={()=>{DemoStore.dispatch(add2ActionCreator(inputRef.current.value))}}>点我数字加</button>
      <button onClick={()=>{DemoStore.dispatch(sub2ActionCreator(inputRef.current.value))}}>点我数字减</button>
      <p>{count}</p></>
    }
    
  • Redux符合严格的单向数据流,即**dispatch派发动作=>调用reducer函数操作State=>合并State形成Store=>返回一个完成的State树,调用所有订阅监听器。**这一数据流即完成了上部分整体流程的实现,并实现了三大原则。

2.React-Redux
  • 在使用时可以根据情况选择是否使用React-Redux进行实现。在React-Redux中存在有两类组件,UI组件与容器组件。Redux直接与容器组件关联,容器组件将从Redux处获取的状态传递给UI组件。在连接中则是使用connect将容器组件与UI连接上。

  • 其中容器组件是通过一个方法生成的。其负责管理数据与业务逻辑,带有内部状态,并使用Redux的API。在使用时,store则是通过父组件向容器组件传递。

    // actions,reducers,store不用更改
    
    export default function Demo(props) {
      return <>
      <CompA/>
      <CompB/>
      {/* 需要引入容器组件,而非原本直接引入组件,并将Store作为store参数传递给容器组件 */}
      <CompCContainer store={DemoStore}/>
      </>
    }
    
    // 没有更改
    function CompA(){
      const inputRef = React.useRef();
    
      return <>
      <input type="number" ref={inputRef} />
      <button onClick={()=>{DemoStore.dispatch(addActionCreator(inputRef.current.value))}}>点我数字加</button>
      <button onClick={()=>{DemoStore.dispatch(subActionCreator(inputRef.current.value))}}>点我数字减</button>
      </>
    }
    
    // 没有更改
    function CompB(){
      // 获取State时方式改变要加上.Reducer进行获取
      const initCount = DemoStore.getState().DemoReducer;
      const [count,setCount] = useState(initCount);
      React.useEffect(()=>{
        // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
        const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState().DemoReducer)});
        // 取消订阅
        return unSubScribe;
      },[]);
      return <p>{count}</p>
    }
    
    // 将接收到的store与容器组件接收到的props传递给UI组件
    function mapStateToProps(state,props){
      return {
        count:state.DemoReducer2,
        ...props
      };
    }
    
    // 将派送给Store的动作传递给UI组件,接收dispatch方法
    function mapDispatchToProps(dispatch,props){
      return {
        CountControl:function(action){
          dispatch(action);
        },
      }
    }
    
    // 使用connect创建容器组件,前项为store中的状态,后项为派送给store的动作
    const CompCContainer = connect(mapStateToProps,mapDispatchToProps)(CompC);
    
    // 去除掉内部state与生命周期函数,直接从props上获取,变成UI组件
    function CompC(props){
      const inputRef = React.useRef();
      console.log('name',props.name);
      return <><input type="number" ref={inputRef} />
      <button onClick={()=>{props.CountControl(add2ActionCreator(inputRef.current.value));props.func()}}>点我数字加</button>
      <button onClick={()=>{props.CountControl(sub2ActionCreator(inputRef.current.value))}}>点我数字减</button>
      <p>{props.count}</p></>
    }
    
3.Redux中间件
  • 在Redux中,可以通过添加中间件,来为Redux增添功能。中间件可以完成在action流中对action进行操作。即在dispatch分发阶段将传入的action进行操作,并传递给下一个reducer。

  • 上述的Redux实现了同步操作,即action作为一个对象出现。当action作为一个函数时,其就变成了一个异步操作。

  • Redux Thunk即应用于异步操作,在使用之前需要先对其进行安装npm install --save redux-thunk。针对1中的代码添加异步操作,可以变为

    import { createStore,applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    
    // 定义常量,防止拼写错误
    const ADD = 'add';
    const SUB = 'sub';
    
    // 定义动作制作器,将接收到的消息处理后,派送给状态仓库
    function addActionCreator(num){
      return {type:ADD,data:num};
    }
    function subActionCreator(num){
      return {type:SUB,data:num};
    }
    
    // 异步动作制作器,返回的不再是对象,而是一个接收绑定函数作为参数的函数
    // 一般情况下,异步动作中都会调用同步动作
    function addAsyncActionCreator(num){
      return (dispatch)=>{
        setTimeout(()=>{
          dispatch(addActionCreator(num));
        },5000);
      };
    }
    
    // 定义默认状态
    const initState = 0;
    
    // 定义操作函数处理状态
    function DemoReducer(preState = initState, action){
      var state = preState;
      switch(action.type){
        case ADD:
          state+=parseInt(action.data);
          break;
        case SUB:
          state-=parseInt(action.data);
      }
      return state;
    }
    
    // 根据reducer,定义状态仓库
    // 引入thunk,并使用applyMiddleware方法将中间件引入
    const DemoStore = createStore(DemoReducer,applyMiddleware(thunk));
    
    export default function Demo(props) {
      return <>
      <CompA/>
      <CompB/>
      </>
    }
    
    function CompA(){
      const inputRef = React.useRef();
    
      return <>
      <input type="number" ref={inputRef} />
      {/* 点击按钮派发动作 */}
      <button onClick={()=>{DemoStore.dispatch(addActionCreator(inputRef.current.value))}}>点我数字加</button>
      <button onClick={()=>{DemoStore.dispatch(subActionCreator(inputRef.current.value))}}>点我数字减</button>
      {/* 正常使用,调用动作发生器并派发动作 */}
      <button onClick={()=>{DemoStore.dispatch(addAsyncActionCreator(inputRef.current.value))}}>点我数字异步加</button>
      </>
    }
    
    function CompB(){
      const initCount = DemoStore.getState();
      const [count,setCount] = useState(initCount);
      React.useEffect(()=>{
        // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
        const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState())});
        // 取消订阅
        return unSubScribe;
      },[]);
      return <p>{count}</p>
    }
    
  • 除了使用redux-thunk,还可以使用Redux-Sage作为异步操作时的中间件,相对于redux-thunk,其不需要为每一个异步操作都定义一个action,并且其提供了声明式的Effect,并使用Generator功能,让异步流程更易于读取,写入与测试,减少回调地狱。但是相应的也更麻烦一点。

    • 安装:npm install --save redux-saga

    • 其中包含有多个Effect方法,即用在yield之后的方法。下面将对一些常用的Effect方法进行使用。

      import { createStore,applyMiddleware } from 'redux';
      import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects';
      import createSagaMiddleware from 'redux-saga';
      
      // 定义常量,防止拼写错误
      const ADD = 'add';
      const SUB = 'sub';
      const ADD_ASYNC = 'addAsync';
      const SUB_ASYNC = 'subAsync';
      
      // 定义动作制作器,将接收到的消息处理后,派送给状态仓库
      function addActionCreator(num){
        return {type:ADD,data:num};
      }
      function subActionCreator(num){
        return {type:SUB,data:num};
      }
      
      // 异步动作制作器
      function addAsyncActionCreator(num){
        return {type:ADD_ASYNC,data:num};
      }
      function subAsyncActionCreator(num){
        return {type:SUB_ASYNC,data:num};
      }
      
      // 定义默认状态
      const initState = 0;
      
      // 定义操作函数处理状态
      function DemoReducer(preState = initState, action){
        var state = preState;
        switch(action.type){
          case ADD:
            state+=parseInt(action.data);
            break;
          case SUB:
            state-=parseInt(action.data);
        }
        return state;
      }
      
      // saga部分
      // 监控部分,使用takeLast只执行最后一次action。并调用对应的函数
      // 也就是说在相同时间内快速点击,只会调用一次
      function* watchAdd(){
        yield takeLatest(ADD_ASYNC,addAsync);
      }
      // 监控部分,使用takeEvery保证并行执行每一次action。并调用对应的函数
      // 也就是说在相同时间内快速点击,每次都会调用
      function* watchSub(){
        yield takeEvery(SUB_ASYNC,subAsync);
      } 
      
      // 执行部分,直接将接收到的动作,通过转换后进行调用,派发给reducer
      function* addAsync(action){
        yield delay(1000);
        yield put({type:ADD,data:action.data});
      } 
      
      function* subAsync(action){
        yield delay(1000);
        yield put({type:SUB,data:action.data});
      } 
      
      // 合并多个sage
      function* countSaga(){
        yield all([watchAdd(),watchSub()]);
      }
      
      // 创建saga
      const sageMiddleware = createSagaMiddleware();
      
      // 根据reducer,定义状态仓库
      const DemoStore = createStore(DemoReducer,applyMiddleware(sageMiddleware));
      
      // 动态运行saga,其位置必须在定义状态仓库之后
      sageMiddleware.run(countSaga);
      
      export default function Demo(props) {
        return <>
        <CompA/>
        <CompB/>
        </>
      }
      
      function CompA(){
        const inputRef = React.useRef();
      
        return <>
        <input type="number" ref={inputRef} />
        {/* 点击按钮派发动作 */}
        <button onClick={()=>{DemoStore.dispatch(addActionCreator(inputRef.current.value))}}>点我数字加</button>
        <button onClick={()=>{DemoStore.dispatch(subActionCreator(inputRef.current.value))}}>点我数字减</button>
        {/* 正常使用,调用动作发生器并派发动作 */}
        <button onClick={()=>{DemoStore.dispatch(addAsyncActionCreator(inputRef.current.value))}}>点我数字异步加</button>
        <button onClick={()=>{DemoStore.dispatch(subAsyncActionCreator(inputRef.current.value))}}>点我数字异步减</button>
        </>
      }
      
      function CompB(){
        const initCount = DemoStore.getState();
        const [count,setCount] = useState(initCount);
        React.useEffect(()=>{
          // 进行订阅,当Redux状态更改时,获取新的值赋予给当前组件状态
          const unSubScribe = DemoStore.subscribe(()=>{setCount(DemoStore.getState())});
          // 取消订阅
          return unSubScribe;
        },[]);
        return <p>{count}</p>
      }
      

参考文章

React 消息订阅与发布机制 - 掘金 (juejin.cn)

React-Redux - 简书 (jianshu.com)

Redux中文文档

深入理解 Redux 中间件 - 简书 (jianshu.com)

轻松使用Redux-saga - 知乎 (zhihu.com)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值