React状态管理 - useState 和 useReducer 如何选择?

更新日期: 2022-03-18阅读: 1.1k标签: React

react 是一个用于构建用户界面的 JavaScript 库,解决了视图 UI 层问题。但是对于状态管理却没有给出一个好的解决方案,状态大致分为:反映 UI 变化的状态、本地创建的状态、服务器返回的数据状态等,面对社区众多的状态管理库,做为初学者不知道如何更好的选择。

本系列文章 "React 状态管理" 也是希望能帮助到大家了解不同状态管理方案(React 官方本身提供的及社区的一些库)能做的事情有哪些及优略点。

本篇先从 React 本身提供的组件状态管理开始。React 的组件状态分为类组件时代的 和当下函数组件的 hooks api 管理数据状态。

类组件

State类组件将组件内所有的状态放在一个 state 中管理: 获取状态数据、 更新状态数据。

import React from 'react';

class State extends React.Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    }
  }

  render() {
    return <div>
      <p>count: {this.state.count}</p>
      <button onClick={() => 
        this.setState({ count: this.state.count + 1 })
      }> 类组件计数 </button>
    </div>
  }
}
export default State;

函数组件 useState

React 16.8 发布后,函数组件成为热门选择,组件状态使用 useState() 函数,相比类组件状态拆分的更细并且每次更新状态变量都是替换而不是合并。

如果更新依赖于先前的状态,可以将函数传递给 setState,并返回一个更新后的值 setState(preState => newState)。 熟悉 Redux 的朋友看起来也许不陌生,该函数是不是更像一个 reducer 函数?

示例:useState 实现计数器增、减、重置。

import { useState } from 'react';
const State = () => {
  const [count, setCount] = useState(0);

  return <div>
    <p>计数器 count: {count}</p>
    <button onClick={() => setCount(count + 1)}> 增 </button>
    <button onClick={() => setCount(preCount => preCount - 1)}> 减 </button>
    <button onClick={() => setCount(0)}> 重置 </button>
  </div>
}
export default State;

函数组件 useReducer

刚开始看到 useReducer 时,还联想和 Redux 之间有什么关系?需要注意的是和 Redux 完全不是一个东西,useReducer 是 React Hooks 提供的一个 API,如果熟悉 Redux 可能看起来会更熟悉。

useReducer 是 useState 的替代方案,主要用来处理一些逻辑比较复杂的 state 或 下一个 state 依赖于前一个 state 等。

Example:使用 useReducer 重写上面的计数器示例,可以和上面的 useState 做一个对比。

import { useReducer } from "react"

const COUNT_INCREMENT = 'COUNT_INCREMENT';
const COUNT_DECREMENT = 'COUNT_DECREMENT';
const COUNT_RESET = 'COUNT_RESET';

const initialState = {
  count: 0,
};

const reducer = (state, action) => {
  switch (action.type) {
    case COUNT_INCREMENT:
      return { count: state.count + 1 };
    case COUNT_DECREMENT:
      return { count: state.count - 1 };
    case COUNT_RESET:
      return { count: initialState.count };
    default:
      return state;
  }
};

const State = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return <div>
    <p>计数器 count: {state.count}</p>
    <button onClick={() => dispatch({ type: COUNT_INCREMENT })}> 增 </button>
    <button onClick={() => dispatch({ type: COUNT_DECREMENT })}> 减 </button>
    <button onClick={() => dispatch({ type: COUNT_RESET })}> 重置 </button>
  </div>
}

export default State;

useReducer 实现 Todos

使用 useReducer 实现一个稍微复杂点的功能,例如 Todos,有创建、更新、移除待办事项功能,你可以试想下如果换做 useState 该怎么处理?会不会更麻烦?

减速机实现

在 目录下实现 reducer 需要的逻辑,定义的 initialState 变量、reducer 函数都是为 useReducer 这个 Hook 做准备的,这里的 reducer 函数是一个纯函数。

// src/reducers/todos-reducer.jsx
export const TODO_LIST_ADD = 'TODO_LIST_ADD';
export const TODO_LIST_EDIT = 'TODO_LIST_EDIT';
export const TODO_LIST_REMOVE = 'TODO_LIST_REMOVE';

const randomID = () => Math.floor(Math.random() * 10000);
export const initialState = {
  todos: [{ id: randomID(), content: 'todo list' }],
};

const reducer = (state, action) => {
  switch (action.type) {
    case TODO_LIST_ADD: {
      const newTodo = {
        id: randomID(),
        content: action.payload.content
      };
      return {
        todos: [ ...state.todos, newTodo ],
      }
    }
    case TODO_LIST_EDIT: {
      return {
        todos: state.todos.map(item => {
          const newTodo = { ...item };
          if (item.id === action.payload.id) {
            newTodo.content = action.payload.content;
          }
          return newTodo;
        })
      }
    }
    case TODO_LIST_REMOVE: {
      return {
        todos: state.todos.filter(item => item.id !== action.payload.id),
      }
    }
    default: return state;
  }
}

export default reducer;

创建 Todos 组件

下面进行待办事项组件开发,细分为 TodoAdd、Todo、Todos 三个组件,在 Todos 组件内使用 useReducer 获取组件需要的状态(state)和更新状态的方法(dispatch)。

添加、更新、删除待办事项行为分别在 TodoAdd、Todo 组件中,以下我们使用的是 React 推荐的 组合组件模式,可以通过 props 从父组件传递数据到子组件。

import { useReducer, useState } from "react";
import reducer, { initialState, TODO_LIST_ADD, TODO_LIST_EDIT, TODO_LIST_REMOVE } from "../../reducers/todos-reducer";

const TodoAdd = ({ dispatch }) => {
  const [content, setContent] = useState('');

  return <>
    <input type="text" onChange={e => setContent(e.target.value)} />
    <button onClick={() => {
      dispatch({ type: TODO_LIST_ADD, payload: { content } })
    }}>
      添加
    </button>
  </>
};

const Todo = ({ todo, dispatch }) => {
  const [isEdit, setIsEdit] = useState(false);
  const [content, setContent] = useState(todo.content);

  return <div>
    {
      !isEdit ? <>
        <span>{todo.content}</span>
        <button onClick={() => setIsEdit(true)}> 编辑 </button>
        <button onClick={() => dispatch({ type: TODO_LIST_REMOVE, payload: { id: todo.id } })}> 删除 </button>
      </> : <>
        <input value={content} type="text" onChange={ e => setContent(e.target.value) } />
        <button onClick={() => {
          setIsEdit(false);
          dispatch({ type: TODO_LIST_EDIT, payload: { id: todo.id, content } })
        }}> 更新 </button>
        <button onClick={() => setIsEdit(false)}> 取消 </button>
      </>
    }
  </div>
}

const Todos = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return <>
    <TodoAdd dispatch={dispatch} />
    {
      state.todos.map(todo => <Todo
        key={todo.id}
        todo={todo}
        dispatch={dispatch}
      />)
    }
  </>
}

export default Todos;

总结

useState 适合定义一些单一的状态,useReducer 适合逻辑比较复杂的状态。

一个好的经验法则是:"如果状态是基本的数据类型(字符串、数值型、布尔型)可以选择 useState,如果是数组或对象等其它复杂的类型或复杂逻辑时最好选择 useReducer"。

但是两者都是组件的状态管理,当多个组件协同工作时不得不面对组件间 "数据通信" 问题。

React 组件数据状态流转是一个 "单项数据流" 的理念。简单的组件可以通过 props 传递数据状态,当组件过多时,层层 Props 传递的方式显的就有点过于繁琐了,不易维护。

来源: 编程界

链接: https://fly63.com/article/detial/11272

如何优雅的设计 React 组件

如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。

React深度编程:受控组件与非受控组件

受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。

React框架学习_关于React两种构建应用方式选择

一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。

Gatsby.js_一款基于React.js静态站点生成工具

Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。

React创建组件的三种方式及其区别

React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件

react生命周期详解_深入理解React生命周期

React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。

React + Webpack 构建打包优化

React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大

react router中页面传值的三种方法

这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state

react 高阶组件的 理解和应用

react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)

react中的refs属性的使用方法

React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!