likes
comments
collection
share

解惑 -- Redux 初学者的四大疑问

作者站长头像
站长
· 阅读数 81

在学习 Redux 的过程中,初学者可能会遇到的四个问题,它们分别是:

  1. mapStatetoProps 和 mapDispatchtoProps 在 React 中到底该如何使用?
  2. Action 触发之后,Redux 究竟是如何告诉组件更新的;更新的策略是什么?
  3. Redux 的异步操作是通过 thunk 实现的,其具体的原理是什么?
  4. 在使用 Redux 的时候,有哪些第三方库可以提供帮助?

本文就带你一站解决上述问题,让你使用 Redux 的之间有一个坚实的基础!

1. mapStatetoProps 和 mapDispatchtoProps

这两个函数是在 React 中的类组件使用 Redux 的时候的远古产物;虽然目前使用的大都是函数式组件。但是了解这两个函数的使用方式和原理也是必要的,说不定哪天就能用到了呢。


在Redux中,mapStateToPropsmapDispatchToProps 是两个非常重要的概念,它们通常与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

  1. 对象形式:每个属性名都对应一个action创建函数,Redux会自动使用dispatch来绑定这些函数。
const mapDispatchToProps = {
  addTodo: addTodoActionCreator
};
  1. 函数形式:接受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 函数内部进行了以下步骤:

  1. 接收一个对象形式的 mapDispatchToProps
  2. 遍历该对象的属性,识别出每个属性都是一个 action 创建函数。
  3. 使用 bindActionCreators 函数将这些 action 创建函数与 dispatch 函数绑定。
  4. 将绑定后的函数作为新的 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,如useSelectoruseDispatch,这使得你可以在不使用connectmapStateToProps/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的状态变化时,所有通过connectuseSelector连接到这个Store的React组件都会收到通知,并自动重新渲染自己,以展示最新的数据。

所以,总结一下:React组件通过React-Redux库与Redux Store连接,当Store的状态变化时,组件会自动重新渲染,以展示最新的数据。

部分渲染原理

当一个组件通过connect函数或useSelector Hook连接到Redux store时,它只会对store中与其相关的那部分状态感兴趣。当那部分状态发生变化时,React-Redux库会确保只有这个组件进行重新渲染,而不是整个应用。

对于useSelector Hook来说,它有一个特殊的机制来避免不必要的重新渲染。即使store中的其他部分状态更新了,但只要useSelector所选择的那部分状态没有变化,使用useSelector的组件就不会重新渲染。这是通过React-Redux的内部实现和优化来保证的。

具体来说。useSelectorconnect 允许 React 组件从 Redux store 中选择状态,并将这些状态作为 props 传递给组件。它们实现部分渲染的关键在于它们的响应式机制和对 React 的生命周期的集成。

1. useSelector

useSelector 是一个 React Hook,它允许函数组件从 Redux store 中选择数据。它是如何实现部分渲染的呢?

  1. 依赖追踪useSelector 接收一个选择器函数作为参数,这个函数描述了从 store 状态中需要选择哪些数据。React-Redux 会追踪这个选择器函数的依赖,并在依赖发生变化时触发重新渲染。
  2. 浅比较:当 store 状态更新时,React-Redux 会对新的状态与旧的状态进行浅比较。如果选择器函数的结果没有变化(即返回的对象引用相同,或者对象的属性值没有变化),那么使用 useSelector 的组件就不会重新渲染。
  3. 高效的更新:由于 useSelector 只会在选择的数据发生变化时触发重新渲染,因此它避免了不必要的渲染,提高了应用的性能。

2. connect

connect 是一个高阶组件(HOC),它接收两个函数作为参数:mapStateToPropsmapDispatchToProps。这两个函数分别定义了如何从 store 中选择状态以及如何创建 action 分派函数。


  1. 状态映射mapStateToProps 函数定义了如何从 store 状态中选择数据,并将这些数据作为 props 传递给被包裹的组件。与 useSelector 类似,React-Redux 会追踪这些选择的依赖,并在依赖发生变化时触发重新渲染。
  2. 生命周期集成connect 利用了 React 的生命周期方法(如 componentDidUpdate)来监听 store 状态的变化。当 store 状态更新时,connect 会重新运行 mapStateToProps 函数,并比较新的 props 与旧的 props。如果它们不同,那么被包裹的组件就会重新渲染。
  3. 性能优化:与 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 可以是一个函数,而不是一个普通的对象。这个函数可以接收 dispatchgetState 作为参数,并在需要的时候调用 dispatch 来分发实际的 action。

具体来说,Redux Thunk 的原理可以分为以下几个步骤:

  1. 中间件机制: 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));
    
  2. 函数作为 action: 由于 Redux Thunk 的存在,我们可以分发一个函数作为 action。这个函数接收 dispatchgetState 作为参数。

    示例代码,一个异步 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 });
          });
      };
    }
    
  3. 异步操作: 在函数内部,我们可以使用 JavaScript 的异步特性(如 Promises、async/await 等)来执行异步操作。当异步操作完成后,我们可以调用 dispatch 方法来分发一个真正的 action。

即上面的 dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data })dispatch({ type: 'FETCH_DATA_FAILURE', error: error.message })

  1. 更新 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 库中的函数: setgetpipe。下面将详细地解释这三个函数的作用和使用方式。

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 方法组织起来使用,是更进一步的抽象。

总结

虽然 setgetpipe 是 Lodash FP 模块中非常有用的函数,在 Redux 应用中使用它们可以直接得到一个新的对西昂,而不必通过多层解构或者合并获得新的对象。在有些场景下,这些方法能够极大的提升我们的编码效率。

转载自:https://juejin.cn/post/7367394078622040116
评论
请登录