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

更新日期: 2022-03-18 阅读: 1.7k 标签: 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

相关推荐

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

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

解决vscode 开发react 导入绝对路径 无法跳转的问题

相对路径可正常跳转,但是在webpack配置alias使用绝对路径后无法跳转.解决办法:需要添加一个jsconfig文件,如下:

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

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

React常用hook的优化useEffect浅比较

先说说react原版的useEffect使用起来不便的地方,这里的effect每次更新都会执行,因为第三个参数一直是不等的,第二是在deps依赖很多的时候是真的麻烦

React 监听页面滚动,界面动态显示

当页面滚动时,如何动态切换布局/样式, 添加滚动事件的监听/注销

React + Webpack 构建打包优化

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

react-router v4 按需加载的配置方法

在react项目开发中,当访问默认页面时会一次性请求所有的js资源,这会大大影响页面的加载速度和用户体验。所以添加按需加载功能是必要的,以下是配置按需加载的方法

React事件处理函数必须使用bind(this)的原因

学习React的过程中发现调用函数的时候必须使用bind(this),之后直接在class中声明函数即可正常使用,但是为什么呢,博主进行了一番查阅,总结如下。

grpc-web与react的集成

使用create-react-app脚手架生成react相关部分,脚手架内部会通过node自动起一个客户端,然后和普通的ajax请求一样,和远端服务器进行通信,只不过这里采用支持rpc通信的grpc-web来发起请求,远端采用docker容器的node服务器,node服务器端使用envoy作为代理

react中的refs属性的使用方法

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

点击更多...

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