解惑 -- Redux 初学者的四大疑问
在学习 Redux 的过程中,初学者可能会遇到的四个问题,它们分别是:
- mapStatetoProps 和 mapDispatchtoProps 在 React 中到底该如何使用?
- Action 触发之后,Redux 究竟是如何告诉组件更新的;更新的策略是什么?
- Redux 的异步操作是通过 thunk 实现的,其具体的原理是什么?
- 在使用 Redux 的时候,有哪些第三方库可以提供帮助?
本文就带你一站解决上述问题,让你使用 Redux 的之间有一个坚实的基础!
1. mapStatetoProps 和 mapDispatchtoProps
这两个函数是在 React 中的类组件使用 Redux 的时候的远古产物;虽然目前使用的大都是函数式组件。但是了解这两个函数的使用方式和原理也是必要的,说不定哪天就能用到了呢。
在Redux中,mapStateToProps
和 mapDispatchToProps
是两个非常重要的概念,它们通常与connect
函数一起使用,以将Redux store中的状态(state)和action创建函数(dispatch functions)映射到React组件的props上。
1.1 mapStateToProps
mapStateToProps
是一个函数,它接收Redux store的当前状态作为参数,并返回一个对象,该对象的属性将被作为props传递给React组件。例如:
const mapStateToProps = (state) => {
return {
todos: state.todos
};
};
在这个例子中,组件将会接收到一个名为todos
的prop,其值来自Redux store的todos
状态。
1.2 mapDispatchToProps
mapDispatchToProps
也是一个函数,它接收dispatch
函数作为参数,并返回一个对象,该对象的属性是action创建函数(或已绑定的action创建函数)。这些函数可以直接在React组件中调用以分派actions。有两种常见的方式来定义mapDispatchToProps
:
- 对象形式:每个属性名都对应一个action创建函数,Redux会自动使用
dispatch
来绑定这些函数。
const mapDispatchToProps = {
addTodo: addTodoActionCreator
};
- 函数形式:接受
dispatch
作为参数,并返回一个对象,你可以在这个函数中手动绑定action创建函数。
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => dispatch(addTodoActionCreator(text))
};
};
前面的做法可以看成是后面做法的一个语法糖。在 React 中使用 Redux 时,mapDispatchToProps
可以写成对象形式的原因在于 Redux 的 connect
函数提供了这种便利的语法糖。这种对象形式的 mapDispatchToProps
是 Redux 的 bindActionCreators
辅助函数的一种简化使用。
当你以对象形式提供 mapDispatchToProps
时,Redux 的 connect
函数内部会调用 bindActionCreators
函数,将你的 action 创建函数(即那些返回 action 对象的函数)与 dispatch
函数进行绑定。这样,当你在组件中调用这些函数时,它们会自动调用 dispatch
函数并将返回的 action 分派到 Redux store。
这种对象形式的 mapDispatchToProps
之所以可行,是因为 Redux 的 connect
函数内部进行了以下步骤:
- 接收一个对象形式的
mapDispatchToProps
。 - 遍历该对象的属性,识别出每个属性都是一个 action 创建函数。
- 使用
bindActionCreators
函数将这些 action 创建函数与dispatch
函数绑定。 - 将绑定后的函数作为新的 props 传递给组件。
这种语法糖使得编写 mapDispatchToProps
更加简洁,尤其是对于简单的 action 创建函数,你不需要显式地编写将 action 创建函数与 dispatch
绑定的逻辑。当然,如果你需要更复杂的绑定逻辑(例如,在绑定之前修改 action 创建函数的参数),你可以使用函数形式的 mapDispatchToProps
来实现。
因此,对象形式的 mapDispatchToProps
是 Redux 提供的一种简化方式,让你能够更快捷地将 action 创建函数绑定到组件的 props 上,而无需显式地编写绑定逻辑。
1.3 简化使用
在React Redux库中,bindActionCreators
是一个很有用的工具函数,它接受一个action创建函数对象,并返回一个所有函数都被dispatch
绑定的新对象。但在现代React Redux应用中,我们通常不需要直接使用bindActionCreators
,因为Redux的connect
函数可以自动处理对象形式的mapDispatchToProps
。
然而,如果你确实需要手动绑定action创建函数(例如,在组件外部或高阶组件中),你可以使用bindActionCreators
。但请注意,在大多数情况下,你只需在mapDispatchToProps
中返回一个对象即可。
1.4 用 Hooks 替代 mapStateToProps/
mapDispatchToProps
从React Redux v7开始,引入了Hooks API,如useSelector
和useDispatch
,这使得你可以在不使用connect
和mapStateToProps
/mapDispatchToProps
的情况下访问Redux store的状态和dispatch函数。这使得代码更加简洁和模块化。
例如:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo } from './actions';
function TodoList() {
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
const handleAddTodo = (text) => {
dispatch(addTodo(text));
};
// ... 组件的其他部分
}
在这个例子中,我们使用useSelector
来从Redux store中选择todos
状态,并使用useDispatch
来获取dispatch
函数。然后,我们定义了一个handleAddTodo
函数来分派addTodo
action。这种方式避免了使用connect
和相关的映射函数。
2. Redux 更新视图的原理
React 中使用 Redux 开发应用时,组件会根据 Redux Store 中的状态变化来重新渲染自己。接下来我用更简单的语言来解释这个过程:
Redux Store和React组件的关系
想象一下Redux Store是一个大仓库,里面存放着应用的所有数据(我们称之为“状态”)。而React组件就像是应用中的小盒子,它们需要展示或操作这些数据。
连接React组件和Redux Store
为了让React组件能够知道Redux Store中的数据变化,我们使用了React-Redux库。这个库提供了两种主要的方式来实现这个连接:connect
函数和Hooks API(特别是useSelector
)。
-
使用
connect
函数:你可以将connect
函数看作是一个桥梁,它将React组件和Redux Store连接起来。通过这个桥梁,你可以告诉React组件:“嘿,我想从Store中取一些数据来用”。当Store中的数据变化时,connect
函数会自动将这个变化通知给组件,组件就会重新渲染自己,以展示最新的数据。 -
使用Hooks API(如
useSelector
):Hooks是React 16.8版本之后引入的新特性,它们允许你在不编写class的情况下使用state以及其他的React特性。useSelector
就是React-Redux提供的一个Hook,你可以直接在函数组件中使用它来从Redux Store中选择需要的数据。当Store中的数据变化时,useSelector
会自动重新运行,并将新的数据传递给组件,从而触发组件的重新渲染。
状态变化与组件重新渲染
当你在应用中执行某个操作(比如点击按钮、输入文本等),这个操作可能会发送一个Action给Redux Store。Store接收到这个Action后,会调用相应的Reducer来处理这个Action,并返回一个新的状态。当Store的状态变化时,所有通过connect
或useSelector
连接到这个Store的React组件都会收到通知,并自动重新渲染自己,以展示最新的数据。
所以,总结一下:React组件通过React-Redux库与Redux Store连接,当Store的状态变化时,组件会自动重新渲染,以展示最新的数据。
部分渲染原理
当一个组件通过connect
函数或useSelector
Hook连接到Redux store时,它只会对store中与其相关的那部分状态感兴趣。当那部分状态发生变化时,React-Redux库会确保只有这个组件进行重新渲染,而不是整个应用。
对于useSelector
Hook来说,它有一个特殊的机制来避免不必要的重新渲染。即使store中的其他部分状态更新了,但只要useSelector
所选择的那部分状态没有变化,使用useSelector
的组件就不会重新渲染。这是通过React-Redux的内部实现和优化来保证的。
具体来说。useSelector
和 connect
允许 React 组件从 Redux store 中选择状态,并将这些状态作为 props 传递给组件。它们实现部分渲染的关键在于它们的响应式机制和对 React 的生命周期的集成。
1. useSelector
useSelector
是一个 React Hook,它允许函数组件从 Redux store 中选择数据。它是如何实现部分渲染的呢?
- 依赖追踪:
useSelector
接收一个选择器函数作为参数,这个函数描述了从 store 状态中需要选择哪些数据。React-Redux 会追踪这个选择器函数的依赖,并在依赖发生变化时触发重新渲染。 - 浅比较:当 store 状态更新时,React-Redux 会对新的状态与旧的状态进行浅比较。如果选择器函数的结果没有变化(即返回的对象引用相同,或者对象的属性值没有变化),那么使用
useSelector
的组件就不会重新渲染。 - 高效的更新:由于
useSelector
只会在选择的数据发生变化时触发重新渲染,因此它避免了不必要的渲染,提高了应用的性能。
2. connect
connect
是一个高阶组件(HOC),它接收两个函数作为参数:mapStateToProps
和 mapDispatchToProps
。这两个函数分别定义了如何从 store 中选择状态以及如何创建 action 分派函数。
- 状态映射:
mapStateToProps
函数定义了如何从 store 状态中选择数据,并将这些数据作为 props 传递给被包裹的组件。与useSelector
类似,React-Redux 会追踪这些选择的依赖,并在依赖发生变化时触发重新渲染。 - 生命周期集成:
connect
利用了 React 的生命周期方法(如componentDidUpdate
)来监听 store 状态的变化。当 store 状态更新时,connect
会重新运行mapStateToProps
函数,并比较新的 props 与旧的 props。如果它们不同,那么被包裹的组件就会重新渲染。 - 性能优化:与
useSelector
一样,connect
也只会在选择的数据发生变化时触发重新渲染,从而避免了不必要的渲染开销。
总之,无论是 useSelector
还是 connect
,它们都是通过依赖追踪和浅比较的机制来实现部分渲染的。这种机制使得 React-Redux 能够只渲染那些真正需要更新的组件部分,从而提高了应用的性能和响应速度。
3. thunk 的原理
Redux Thunk 的原理主要涉及到异步操作在 Redux 中的处理。在标准的 Redux 流程中,action 是一个普通的 JavaScript 对象,它被 dispatch 到 store 中,然后由 reducer 根据 action 的 type 来更新 state。但是,当涉及到异步操作时(如 API 调用),标准的 Redux 流程就显得力不从心了。
重点:Redux Thunk 的核心在于它允许 action 创建函数返回一个函数,而不是直接返回一个 action 对象。这使得我们可以在返回的函数中执行异步操作,并在操作完成后 dispatch 实际的 action。
这时,Redux Thunk 就派上了用场。它的核心思想是让 action 可以是一个函数,而不是一个普通的对象。这个函数可以接收 dispatch
和 getState
作为参数,并在需要的时候调用 dispatch
来分发实际的 action。
具体来说,Redux Thunk 的原理可以分为以下几个步骤:
-
中间件机制: Redux Thunk 是一个中间件,它拦截了所有被 dispatch 的 action。当一个 action 被 dispatch 时,Redux Thunk 会检查这个 action 是否是一个函数。
安装和配置 Redux Thunk 中间件的代码示例:
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; const store = createStore(rootReducer, applyMiddleware(thunk));
-
函数作为 action: 由于 Redux Thunk 的存在,我们可以分发一个函数作为 action。这个函数接收
dispatch
和getState
作为参数。示例代码,一个异步 action 创建函数:
function fetchDataAsync() { // 返回一个函数,这是 Redux Thunk 的核心 return function(dispatch) { // 使用异步逻辑,例如 fetch API fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { // 当异步操作完成时,dispatch 一个 action dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data }); }) .catch(error => { // 处理错误,也可以 dispatch 一个 action dispatch({ type: 'FETCH_DATA_FAILURE', error: error.message }); }); }; }
-
异步操作: 在函数内部,我们可以使用 JavaScript 的异步特性(如 Promises、async/await 等)来执行异步操作。当异步操作完成后,我们可以调用
dispatch
方法来分发一个真正的 action。
即上面的 dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data })
和 dispatch({ type: 'FETCH_DATA_FAILURE', error: error.message })
- 更新 state: 当异步操作完成后分发的 action 被 reducer 捕获时,reducer 会根据这个 action 的 type 来更新 state。由于 Redux 的单向数据流特性,这个更新会触发组件的重新渲染,从而反映出异步操作的结果。
function dataReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_DATA_SUCCESS':
return { ...state, data: action.payload, isLoading: false, error: null };
case 'FETCH_DATA_FAILURE':
return { ...state, isLoading: false, error: action.error };
// ... 其他 case
default:
return state;
}
}
通过这种方式,Redux Thunk 允许我们在 Redux 中处理异步操作,同时保持了 Redux 架构的清晰和可预测性。它本身并不包含任何异步代码或逻辑,只是提供了一种机制,利用这种机制就可以在 Redux 中更方便地处理异步操作。
4. Redux 和 lodash
介绍和 Redux 使用结合密切的三个 lodash 库中的函数: set
、get
和 pipe
。下面将详细地解释这三个函数的作用和使用方式。
4.1. Lodash FP 的 set
函数
作用:set
函数用于在对象的嵌套属性中设置值。它接受三个参数:要设置的属性的路径(可以是字符串或路径数组)、要设置的值以及要修改的对象。
使用方式:
import set from 'lodash/fp/set';
const obj = { a: { b: { c: 3 } } };
const updatedObj = set('a.b.c', 4, obj);
console.log(updatedObj); // { a: { b: { c: 4 } } }
与 Redux 结合:虽然不常见,但你可以在 reducer 中使用 set
函数来创建新的状态对象,而不是直接修改原始状态。
4.2. Lodash FP 的 get
函数
作用:get
函数用于从对象的嵌套属性中获取值。如果指定的路径不存在,则返回 undefined
或提供的默认值。
使用方式:
import get from 'lodash/fp/get';
const obj = { a: { b: { c: 3 } } };
const value = get('a.b.c', obj);
console.log(value); // 3
与 Redux 结合:在 Redux 应用中,你通常会在 selector 函数或组件中使用 get
(或更常见的是直接访问属性)来获取状态树中的特定值。
4.3. Lodash FP 的 pipe
函数
作用:pipe
函数用于组合多个函数,从右到左执行这些函数。每个函数的输出都会作为下一个函数的输入。
使用方式:
import pipe from 'lodash/fp/pipe';
import multiplyBy from './multiplyBy'; // 假设这是一个将数字乘以给定值的函数
import add from './add'; // 假设这是一个将两个数字相加的函数
const result = pipe(multiplyBy(2), add(3))(5); // 相当于 add(3, multiplyBy(2)(5))
console.log(result); // 13
与 Redux 结合:pipe
函数可以将多个 get 或者 set 方法组织起来使用,是更进一步的抽象。
总结
虽然 set
、get
和 pipe
是 Lodash FP 模块中非常有用的函数,在 Redux 应用中使用它们可以直接得到一个新的对西昂,而不必通过多层解构或者合并获得新的对象。在有些场景下,这些方法能够极大的提升我们的编码效率。
转载自:https://juejin.cn/post/7367394078622040116