react-redux 基本原理 & 使用redux 处理异步逻辑

Redux 出现的背景

随着对 React 使用的深入,你会发现组件级别的 state,和从上而下传递的 props 这两个状态机制,无法满足复杂功能的需要,例如跨层级之间的组件的数据共享和传递,Redux应运而生。

state应用 & redux应用对比

左图是单个 React 组件,它的状态可以用内部的 state 来维护,而且这个 state 在组件外部是无法访问的。
右图则是使用 Redux 的场景,用全局唯一的 Store 维护了整个应用程序的状态。对于页面的多个组件,都是从这个 Store 中获取状态的,从而保证组件之间能够共享状态。

从这张对比图,我们可以看到 Redux Store 的两个特点:

  • Redux Store 是全局唯一的。即整个应用程序一般只有一个 Store。
  • Redux Store 是树状结构,可以更天然地映射到组件树的结构,虽然不是必须的。

有两个场景可以典型地体现出这些特点:

  • 跨组件的状态共享:当某个组件发起一个请求时,将某个 Loading 的数据状态设为 True,另一个全局状态组件则显示 Loading 的状态。
  • 同组件多个实例的状态共享:某个页面组件初次加载时,会发送请求拿回了一个数据,切换到另外一个页面后又返回,这时数据已经存在,无需重新加载。设想如果是本地的组件 state,那么组件销毁后重新创建,state 也会被重置,就还需要重新获取数据。

Redux的三个基本概念

Redux 主要引入了以下三个概念:

  • State: StateStore,一般就是一个纯 JavaScript Object。
  • Action: 是一个 Object,用于描述发生的动作。
  • Reducer: 是一个函数,接收 ActionState 并作为参数,通过计算得到新的 Store (每次返回一个新的对象)

state & action & reducer之间的关系

所有对于Store的修改都必须通过Reducer去完成,这样一方面能保证数据的不可变性(Immutable),同时也带来了可预测性和易于调试的特点 。

  • 可预测性 - 给定一个初始状态和一系列Action,一定能得到一致的结果。
  • 易于调试 - 基于浏览器插件的开发工具可以跟踪 Store 中数据的变化并进行暂停、回放。

如何在React中使用Redux: react-redux

在实际场景中,Redux Store 中的状态最终一定是会体现在 UI 上的,即通过 React 组件展示给用户。那么如何建立 Redux 和 React 的联系呢?

  • React 组件能够在依赖的 Store 的数据发生变化时,重新 Render;
  • 在 React 组件中,能够在某些时机去 dispatch 一个 action,从而触发 Store 的更新。

Facebook 提供的 react-redux 工具库,作用就是建立一个桥梁,让 React 和 Redux 实现互通。

  • 利用了 React 的 Context 机制去存放 Store 的信息。
import React from 'react'
import ReactDOM from 'react-dom'

import { Provider } from 'react-redux'
import store from './store'

import App from './App'

const rootElement = document.getElementById('root')
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)

Provider 组件作为整个应用程序的根节点,并将 Store 作为属性传给了这个组件,这样所有下层的组件就都能够使用 Redux 了。

在函数组件中使用 Redux 就非常简单了:
利用 react-redux 提供的 useSelectoruseDispatch 这两个 Hooks。

  • useSelector 让一个组件能够在 Store 的某些数据发生变化时重新 render。
  • useDispatch 返回 dispatch,让组件能够 dispatch 一些 action 从而修改Store
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

export function Counter() {
  // 从 state 中获取当前的计数值
  const count = useSelector(state => state.value)

  // 获得当前 store 的 dispatch 方法
  const dispatch = useDispatch()

  // 在按钮的 click 时间中去分发 action 来修改 store
  return (
    <div>
      <button
        onClick={() => dispatch({ type: 'counter/incremented' })}
      >+</button>
      <span>{count}</span>
      <button
        onClick={() => dispatch({ type: 'counter/decremented' })}
      >-</button>
    </div>
  )
}
react-redux的单向数据流

使用Redux处理异步逻辑

处理异步逻辑也常常被称为异步 Action,它几乎是 React 面试中必问的一道题,可以认为这是 Redux 使用的进阶场景。

只有能够解释清楚异步 Action,才算是真正理解了 Redux

例:异步场景-发送请求获取数据

不推荐:在函数组件中发送请求 - Store 完全作为一个存放数据的地方

function DataList() {
const dispatch = useDispatch();
// 在组件初次加载时发起请求
useEffect(() => {
  // 请求发送时
  dispatch({ type: 'FETCH_DATA_BEGIN' });
  fetch('/some-url').then(res => {
    // 请求成功时
    dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
  }).catch(err => {
    // 请求失败时
    dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
  })
}, []);

// 绑定到 state 的变化
const data = useSelector(state => state.data);
const pending = useSelector(state => state.pending);
const error = useSelector(state => state.error);

// 根据 state 显示不同的状态
if (error) return 'Error.';
if (pending) return 'Loading...';
return <Table data={data} />;
}

很显然,发送请求获取数据并进行错误处理这个逻辑是不可重用的。假设我们希望在另外一个组件中也能发送同样的请求,就不得不将这段代码重新实现一遍。因此,Redux 中提供了 middleware 这样一个机制,让我们可以巧妙地实现所谓异步 Action 的概念。

推荐:使用 middlewate - dispatch 一个函数用于发送请求

  • 在创建 redux store时指定 redux-thunk 中间件
    import { createStore, applyMiddleware } from 'redux'
    import thunkMiddleware from 'redux-thunk'
    import rootReducer from './reducer'
    
    const composedEnhancer = applyMiddleware(thunkMiddleware)
    const store = createStore(rootReducer, composedEnhancer)
    
  • dispatch actiondispatch一个函数
    import fetchData from './fetchData';
    
    function DataList() {
      const dispatch = useDispatch();
      // dispatch 了一个函数由 redux-thunk 中间件去执行
      dispatch(fetchData());
    }
    
  • fetchData定义
    function fetchData() {
      return dispatch => {
        dispatch({ type: 'FETCH_DATA_BEGIN' });
        fetch('/some-url').then(res => {
          dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
        }).catch(err => {
          dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
        })
      }
    }
    

这一套结合 redux-thunk 中间件的机制,我们就称之为异步 Action。

所以说异步 Action 并不是一个具体的概念,而可以把它看作是 Redux 的一个使用模式。它通过组合使用同步 Action ,在没有引入新概念的同时,用一致的方式提供了处理异步逻辑的方案。

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

推荐阅读更多精彩内容