Redux 出现的背景
随着对 React 使用的深入,你会发现组件级别的 state
,和从上而下传递的 props
这两个状态机制,无法满足复杂功能的需要,例如跨层级之间的组件的数据共享和传递,Redux应运而生。
左图是单个 React 组件,它的状态可以用内部的 state 来维护,而且这个 state 在组件外部是无法访问的。
右图则是使用 Redux 的场景,用全局唯一的 Store 维护了整个应用程序的状态。对于页面的多个组件,都是从这个 Store 中获取状态的,从而保证组件之间能够共享状态。
从这张对比图,我们可以看到 Redux Store 的两个特点:
- Redux Store 是全局唯一的。即整个应用程序一般只有一个 Store。
- Redux Store 是树状结构,可以更天然地映射到组件树的结构,虽然不是必须的。
有两个场景可以典型地体现出这些特点:
- 跨组件的状态共享:当某个组件发起一个请求时,将某个
Loading
的数据状态设为True
,另一个全局状态组件则显示Loading
的状态。 - 同组件多个实例的状态共享:某个页面组件初次加载时,会发送请求拿回了一个数据,切换到另外一个页面后又返回,这时数据已经存在,无需重新加载。设想如果是本地的组件 state,那么组件销毁后重新创建,state 也会被重置,就还需要重新获取数据。
Redux的三个基本概念
Redux 主要引入了以下三个概念:
-
State
:State
即Store
,一般就是一个纯 JavaScript Object。 -
Action
: 是一个Object
,用于描述发生的动作。 -
Reducer
: 是一个函数,接收Action
和State
并作为参数,通过计算得到新的Store
(每次返回一个新的对象)
所有对于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
提供的 useSelector
和 useDispatch
这两个 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>
)
}
使用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
action
时dispatch
一个函数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 ,在没有引入新概念的同时,用一致的方式提供了处理异步逻辑的方案。