我今天分分钟就理解了react中的reducer

Posted 糖~豆豆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我今天分分钟就理解了react中的reducer相关的知识,希望对你有一定的参考价值。

什么是 reducer 函数? 为什么要用 reducer?

  • Reducer 是处理状态的另一种方式。通俗来讲,就是可以让你的复杂组件更加干净,代码更加优雅
  • 当你的组件里有好多个状态更新逻辑,并且有些是有一定关联性的,写多个useState会看起来很杂乱,为解决这个问题,我们可以将多个状态更新逻辑整合到一个外部函数,这个函数就是reducer
  • 整合业务逻辑

使用 reducer 整合状态逻辑

举个栗子:


从上图我们可以看到,目前这个组件有三个处理逻辑,我们写了三个处理函数,这个组件的每个事件处理程序都通过 setTasks 来更新状态。随着这个组件的不断迭代,其状态逻辑也会越来越多。现在看起来没有什么毛病,但是真实业务场景里,需求往往会更加复杂,我们可以使用下面的方式来让业务逻辑更加清晰明了:

将状态逻辑移到组件之外 reducer 函数中

  • 将设置状态的逻辑 修改 成 dispatch 的一个 action;
  • 编写 一个 reducer 函数;
  • 在你的组件中 使用 reducer

看了上面我画的图,是不是感觉reducer很简单哇~ 在真实项目中,我们通常会使用 switch 语句来编写reducer,就像下图

既然我们会写了,那该怎么使用呢?

代码

tasksReducer.js

export default function tasksReducer(tasks, action) 
  switch (action.type) 
    case \'added\': 
      return [
        ...tasks,
        
          id: action.id,
          text: action.text,
          done: false,
        ,
      ];
    
    case \'changed\': 
      return tasks.map((t) => 
        if (t.id === action.task.id) 
          return action.task;
         else 
          return t;
        
      );
    
    case \'deleted\': 
      return tasks.filter((t) => t.id !== action.id);
    
    default: 
      throw Error(\'未知 action:\' + action.type);
    
  


App.js

import  useReducer  from \'react\';
import AddTask from \'./AddTask.js\';
import TaskList from \'./TaskList.js\';
import tasksReducer from \'./tasksReducer.js\';

export default function TaskApp() 
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  function handleAddTask(text) 
    dispatch(
      type: \'added\',
      id: nextId++,
      text: text,
    );
  

  function handleChangeTask(task) 
    dispatch(
      type: \'changed\',
      task: task,
    );
  

  function handleDeleteTask(taskId) 
    dispatch(
      type: \'deleted\',
      id: taskId,
    );
  

  return (
    <>
      <h1>我今天要做什么呢?</h1>
      <AddTask onAddTask=handleAddTask />
      <TaskList
        tasks=tasks
        onChangeTask=handleChangeTask
        onDeleteTask=handleDeleteTask
      />
    </>
  );


let nextId = 3;
const initialTasks = [
  id: 0, text: \'吃饭\', done: true,
  id: 1, text: \'睡觉\', done: false,
  id: 2, text: \'打豆豆\', done: false,
];

对比 useState 和 useReducer

我们观察上图,不难发现:

  • 参数:useReducer 和 useState 很相似,都有初始状态,useReducer 钩子接受 2 个参数: reducer 函数,初始的 state
  • 返回值:useReducer 会返回一个有状态的值和一个设置该状态的函数(在上面案例中就是 dispatch 函数)
  • 代码体积: 多事件相似方式修改 state 时,useReducer 可减少代码量
  • 可读性: 状态更新逻辑简单我们就可以用useState ,逻辑复杂,useReducer 可以将状态更新逻辑与事件处理程序分离
  • 可调试性: useState 出现问题不太好调试,而使用 useReducer 时, 可以在 reducer 函数打印日志

如何编写一个优雅的 reducer

  • reducers 必须纯粹。 即当输入相同时,输出也是相同的。它们不应该包含异步请求、定时器或者任何副作用(对组件外部有影响的操作)。
  • 每个 action 都描述了一个单一的用户交互,即使它会引发数据的多个变化。 举个例子,如果用户在一个由 reducer 管理的表单(包含五个表单项)中点击了 重置按钮,那么 dispatch 一个 reset_form 的 action 比 dispatch 五个单独的 set_field 的 action 更加合理。

还可以使用 Immer 简化 reducers

useImmerReducer 让你可以通过 push 或 arr[i] = 来修改 state。下图描述了,优化前后,代码确实有所减少哦~~

Reducers 应该是纯净的,而 Immer 提供了一种特殊的 draft 对象,可以通过它安全修改 state。Immer 在底层基于当前 state 创建一个副本。

今天就写到这里啦~

  • 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
  • 大家要天天开心哦

欢迎大家指出文章需要改正之处~
学无止境,合作共赢

欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

redux/react 应用程序中的 state 有一个带有 reducer 名称的属性

【中文标题】redux/react 应用程序中的 state 有一个带有 reducer 名称的属性【英文标题】:State in redux/react app has a property with the name of the reducer 【发布时间】:2016-06-10 14:47:40 【问题描述】:

我正在使用 Redux 和 React 创建一个应用程序。我遇到了一个问题,我无法将状态映射到组件属性,因为状态具有与我使用的减速器名称匹配的属性。

根reducer是用combineReducers方法创建的

const rootReducer = combineReducers(
  appReducer
);

初始状态是

const initialState = 
  sources: [], 
  left: , 
  right: ,
  diff:  

然而在组件函数mapStateToProps:

function mapStateToProps(state) 
  return 
    sources: state.sources
  

state.sourcesundefined,因为state 参数的值是


  appReducer: 
    sources: [], 
    left: , 
    right: , 
    diff: 
  

这是 redux 的一个特性吗?所以当我使用更多的reducer时,它们都会向state变量添加新属性?还是我这边有什么问题(我从未在 redux 教程中注意到这种行为)。

谢谢

【问题讨论】:

你的代码是正确的state.appReducer. sources你需要reducer名字 假设你有 2,3 个 reducer,每个 reducer 都有 sources 属性 你可以通过state.appReducer. sources和`state.appReducer.2 sources`获取具体的sources 你所描述的是combineReducers所做的一部分。 您需要在 appReducer 中设置“state = initialState.sources”来更新/访问特定状态 【参考方案1】:

如果你只有一个 reducer,你就不需要combineReducers()。直接使用即可:

const initialState = 
  sources: [],
  left: ,
  right: 

function app(state = initialState, action) 
  switch (action.type) 
  case 'ADD_SOURCE':
    return Object.assign(, state, 
      sources: [...state.sources, action.newSource]
    )
  case 'ADD_SOURCE_TO_LEFT':
    return Object.assign(, state, 
      left: Object.assign(, state.left, 
        [action.sourceId]: true
      )
    )
  case 'ADD_SOURCE_TO_RIGHT':
    return Object.assign(, state, 
      right: Object.assign(, state.right, 
        [action.sourceId]: true
      )
    )
  default:
    return state
  

现在您可以使用该 reducer 创建一个商店:

import  createStore  from 'redux'
const store = createStore(app)

并将组件连接到它:

const mapStateToProps = (state) => (
  sources: state.sources
)

但是,您的 reducer 很难阅读,因为它会同时更新许多不同的内容。现在,this 是您想要将其拆分为多个独立减速器的时刻:

function sources(state = [], action) 
  switch (action.type) 
  case 'ADD_SOURCE':
    return [...state.sources, action.newSource]
  default:
    return state
  


function left(state = , action) 
  switch (action.type) 
  case 'ADD_SOURCE_TO_LEFT':
    return Object.assign(, state, 
      [action.sourceId]: true
    )
  default:
    return state
      


function right(state = , action) 
  switch (action.type) 
  case 'ADD_SOURCE_TO_RIGHT':
    return Object.assign(, state, 
      [action.sourceId]: true
    )
  default:
    return state
      


function app(state = , action) 
  return 
    sources: sources(state.sources, action),
    left: left(state.left, action),
    right: right(state.right, action),
  

这样更容易维护和理解,也更容易独立更改和测试reducer。

最后,作为最后一步,我们可以使用combineReducers()来生成根app reducer,而不是手写:

// function app(state = , action) 
//   return 
//     sources: sources(state.sources, action),
//     left: left(state.left, action),
//     right: right(state.right, action),
//   
// 

import  combineReducers  from 'redux'
const app = combineReducers(
  sources,
  left,
  right
)

使用combineReducers() 代替手动编写根化简器并没有太大的好处,只是它的效率稍高一些,并且可能会为您节省一些拼写错误。此外,您可以在您的应用中多次应用此模式:可以以嵌套方式多次将不相关的 reducer 组合成一个 reducer。

所有这些重构都不会对组件产生影响。

我建议您观看我的free Egghead course on Redux,它涵盖了这种reducer 组合 模式,并展示了combineReducers() 是如何实现的。

【讨论】:

感谢 Dan 的解释以及这些视频的链接。【参考方案2】:

其实我相信你的初始状态应该是:


  appReducer: 
    sources: [],
    left: ,
    right: ,
    diff: 
  

这是因为combineReducers 通过获取reducer 的名称并将其内容映射到该名称来工作。

另外,请注意,但如果您要使用超过 1 个减速器,则减速器的名称应该比 appReducer 更具体,并且(只是我个人的看法)它们不需要这个词reducer。一个典型的应用程序可能如下所示:

combineReducers(
  user: userReducer,
  messages: messagesReducer,
  notifications: notificationsReducer
);

然后,您的状态可以通过以下方式访问:

state.user.email
state.messages[0]

【讨论】:

你是对的。然后我应该相应地更改initialState 对象吗? 这个想法是reducer的每个分支都负责自己的initialState。你的根减速器应该没有初始状态,每个减速器应该只包含它的东西。所以你的appReducer 将是一个带有源、左、右等键的对象。

以上是关于我今天分分钟就理解了react中的reducer的主要内容,如果未能解决你的问题,请参考以下文章

react-redux 项目中的高阶 reducer

redux/react 应用程序中的 state 有一个带有 reducer 名称的属性

redux 中的 action、reducer 和 store 有啥区别?

React.js/Redux:Reducers 中的 setInterval 和 clearInterval

React 的setState 理解

React下reducer中处理数组&&对象的赋值改动