07-05-Redux

课程目标

  • 掌握 redux 三大原则;
  • 掌握 redux 基础使用;
  • 掌握 react-redux 使用;
  • 掌握 redux-thunk 使用。

内容

redux

安装

  • npm i redux,或者 yarn add redux。

核心概念

理解 redux 几个核心概念与它们之间的关系:
state 状态
reducer 纯函数 - 提供修改状态的方法
  • 相同的输入,永远返回相同的输出;
  • 内部不能有任何副作用处理;
  • 该函数结果只依赖自身参数,而不依赖任何外部数据。
store 仓库 - 管理状态:
  • getState:获取当前状态;
  • dispatch:发起一个 action;
  • 修改 state,通过 dispatch 发起 action 之后,在 store 中,会调用 reducer 函数,并且会将 state 和 action 传递给 reducer,reducer 被调用之后,会返回新的 state;
  • subscribe:
    • 类似于 js 中的 addEventListener,当 store 的 state 发生改变时,就会触发 subscribe 中的监听函数;
    • 会返回一个函数,当调用这个返回函数时会取消监听;
  • replaceReducer:替换 reducer。
action 动作 - 对 state 的修改动作,action 本身是一个普通对象,该对象有 type(固定) 属性和 payload 属性
  • action 属性是对 state 做出何种修改的描述;
  • payload:附带参数。
state 对象
  • 通常我们会把应用中的数据存储到一个对象树(Object Tree)中进行统一管理,我们把这个对象树称为:state;
state 是只读的
  • 这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据,而是通过纯函数 reducer 修改 state。
action 对象
  • 我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作;
  • action 是一个对象:
    • type 属性:表示要进行操作的动作类型,增删改查...(type 名字固定);
    • payload 属性:操作 state 的同时传入的数据。
  • 这里需要注意的是,我们不直接去调用 reducer 函数,而是通过 store 对象提供的 dispatch 方法来调用。
store 对象
  • 为了对 state,reducer,action 进行统一管理和维护,我们需要创建一个 store 对象。

redux 三大原则

  • 单一数据源:整个应用的 state 被存储在一颗 Object Tree 中,并且这个 Object Tree 只存在于唯一一个 store 中;
  • state 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
  • 使用纯函数来执行修改。
    import { createStore } from 'redux';
    
    const fn = (state={
      count: 1,
      num: 5
    }, action) => {
      switch (action.type) {
        case 'addNum':
          return ({
            ...state,
            num: state.num+1,
          })
        case 'addCount':
          return ({
            ...state,
            count: state.count+1,
          })
        default:
          return state;
      }
    }
    
    const store = createStore(fn);
    store.dispatch({type: 'addNum'});
    const state = store.getState();
    console.log('store: ', state);
    
    const unSubscribe = store.subscribe(() => {
      console.log('subscribe: ', store.getState())
    });
    store.dispatch({type: 'addNum'})
    store.dispatch({type: 'addNum'})
    // unSubscribe();
    store.replaceReducer((state={count: 10}, action) => {
      switch (action.type) {
        case 'addCount':
          return {...state, count: state.count+10}
        default: 
          return state;
      }
    })
    store.dispatch({type: 'addCount'})
    

redux API

createStore(reducer[,preloadedState],enhancer);
  • reducer:function,接受两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树;
  • preloadedState:初始的 state,在同构应用中,你可以决定是否把服务端传来的 state 传给它,或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容;
  • enhancer:function,store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口;
  • 返回值(store):保存了应用所有 state 的对象,改变 state 的唯一方法是 dispatch action,你也可以 subscribe 监听 state 的变化,然后更新 UI。
reducer
  • reducer(state,action);
store
  • getState();
  • dispatch(action);
  • subscribe(listener);
  • replaceReducer(nextReducer);
combineReducers(reducers) 将 reducer 函数拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分;
applyMiddleware(...middlewares) 中间件。

react-redux

react 项目中的 redux 绑定库
  • 安装:npm i react-redux;
  • <Provider store>
    // store.js
    // redux 不会给我们进行浅合并,所以需要将之前的 state 一并返还
    import { createStore } from 'redux';
    
    const count = (state={
      count: 1,
      num: 5,
      todo: [],
      topics: {}
    }, action) => {
      switch (action.type) {
        case 'addCount':
          return {
            ...state,
            count: state.count + 1,
          }
        case 'addNum':
          return {
            ...state,
            num: state.num + 1,
          }
      }
    }
    const store = createStore(count);
    export default store;
    
    // index.js
    import { Provider } from 'react-redux'; // 版本 7.0 之后增加了 hook
    import App from "./App";
    import store from './store';
    function ReduxTest() {
      return (  
        <Provider store={store}>
          <App/>
        </Provider>
      );
    }
    
    export default ReduxTest;
    
  • connect() —— 高阶函数:传入数据,返回一个函数;
  • useDispatch 获取 dispatch;
  • useStore 获取 store;
  • useSelector 获取 state 中需要的数据。
    // count.js
    /**
     * react-redux 中提供的解决方案
     * - connect
     */
    
    // const withConnect = connect(select: state => {
    //   return {
    //     count: state.count
    //   }
    // })
    // select 函数的作用:从 state 中截取该组件需要的部分,注意返回值是 state 中我们所需要的部分,注意类型必须是一个对象;
    // withConnect:高阶组件,调用该高阶组件,传入组件(Count)会被高阶组件调用返回一个新组件(NewCount),NewCount 被调用,Count 可以获取到截取后的 state,以及 dispatch 方法
    
    import { useCallback } from 'react';
    import { connect } from 'react-redux';
    function Count(props) {
      const { count, dispatch } = props;
    
      const addCount = useCallback(() => {
        dispatch({type: 'addCount'})
      },[]);
      
      return (  
        <div>
          <p>Count: {count}</p>
          <button onClick={addCount}>count-递增</button>
        </div>
      );
    }
    
    // const withConnect = connect(state=>state);
    // const NewCount = withConnect(Count);
    // export default NewCount;
    export default connect(state=>state)(Count);
    
    // num.js
    import { useCallback } from "react";
    import { useDispatch, useSelector, useStore } from "react-redux";
    
    /**
     * useDispatch 获取 dispatch;
     * useStore 获取 store; // 开发中用的较少
     * useSelector 获取 state 中需要的数据。
     */
    
    function Num() {
      const { num } = useSelector(state => state) || {};
      const dispatch = useDispatch();
      const addNum = useCallback(() => {
        dispatch({type:'addNum'});
      }, []);
    
      console.log('store: ', useStore())
      return (  
        <div>
          <p>Num: {num}</p>
          <button onClick={addNum} >num-递增</button>
        </div>
      );
    }
    
    export default Num;
    
    // store.js
    // redux 不会给我们进行浅合并,所以需要将之前的 state 一并返还
    import { createStore, combineReducers } from 'redux';
    import count from './reducers/count';
    import num from './reducers/num';
    import todos from './reducers/todos';
    import topics from './reducers/topics';
    
    // function reducer(state={
    //   count: 1,
    //   num: 5,
    //   todos: [],
    //   topics: {}
    // }, action) {
    //   return {
    //     count: count(state.count, action),
    //     num: num(state.num, action),
    //     todos: todos(state.todos, action),
    //     topics: topics(state.topics, action)
    //   }
    // }
    
    // 使用 combineReducers 实现
    // const reducer = combineReducers({
    //   count,
    //   num,
    //   todos,
    //   topics
    // })
    // const store = createStore(reducer);
    // export default store;
    
    // 注意:action.type 要做到全局唯一
    export default createStore(combineReducers({count, num, todos, topics}))
    

中间件

  • 更新的过程中,去做一些其它的事情,dispatch ——> reducer 更新 state dispatch ——> 中间件 ——> reducer

异步操作中间件

redux-thunk
参数是对象,直接调用 reducer 修改我们的 state
参数是函数,调用该函数,并且把 dispatch 和 getState 传递给我们的函数,可以在函数中,进行异步操作。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • redux的适用场景 页面有很多交互, 有太多props需要在组件树中传递 一个页面有多个ajax 下面是redu...
    aALl阅读 229评论 0 0
  • redux学习笔记 学习动机 随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任...
    flura阅读 349评论 0 0
  • 前言 记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、stor...
    _lahne阅读 2,427评论 0 8
  • 本周在阅读redux源码时,发现一个文章,由简至深,甚好,原文地址在这里,学习一下。 但是本篇内容并不是介绍red...
    DC_er阅读 938评论 0 2
  • Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行...
    水落斜阳阅读 295评论 0 2