从零开始学习React-5:状态与状态管理
theme: devui-blue
携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
通过前面的几篇文章,我们已经大致的了解了 React 中的一些概念,也通过 cra 脚手架创建了一个正式的项目,接下来我们将使用 Antd 组件库开发一些简单的用例。并通过这个例子进一步了解 React 函数式组件、HOOK 、状态管理等概念。
在这个系列文章的第一章,我们就介绍过了 React 的状态,以及一种状态管理思想:状态提升,但是在面对一些复杂场景,状态提升就显得有些麻烦了,多个不同层级的组件状态,提升到共有的顶层组件,然后通过 props 在组件之间传递,一来代码量上提升很多,二来如果涉及修改,就会比较麻烦。
有的中间组件可能并不需要使用这些状态,或者函数,但是为了传递到目标组件还是需要在 props 中进行声明,显得非常的笨重。我写了一个 TodoList 的小例子,来说明这种尴尬的情况。
通过状态提升管理状态
1 | const App = () => { |
可以看出顶层组件 App
不得已承担了状态管理的工作,由他持有状态,分发状态、改变状态的函数给其子组件。
其中 List
组件,虽然他本身并不使用 delTodo
,但是因为 状态提升 的原因,Item
组件想要拿到delTodo
函数,必须由他中转。
这种组织、管理状态的思想称之为:单向数据流,即状态(数据)从父组件向下流向子组件,数据只有一个唯一可信源,就是来自父组件的状态。子组件从过向上传递事件(通过调用由父组件传递的改变状态的函数实现传递),来改变状态。
可以预想,当一个页面存在诸多复杂状态、组件嵌套时,使用状态提升反而会让代码难以管理,难以预测。
这时候就不得不请出 React 中最流行的状态管理库:Redux
使用Redux管理状态
0.安装依赖与原理图
执行:yarn add redux react-redux
安装 redux 与 react-redux。
redux 的理念其实很简单,由如下三个关键点:
- reducer 状态处理函数
- store 状态中心
- action 动作
一句话总结其工作原理就是:通过在组件中调用dispatch
函数传递一个 action
对象给store
,store
通过传入旧的状态
与action
给reducer
函数,获取到新的状态。
1. 类式组件中的使用
1、创建对应状态的 reducer
文件,这里我们称之为 todo.reducer.js
,reducer
在redux中是一个特殊的纯函数,可以理解为state的加工工厂,专门用于处理state。
reducer
函数要求接受两个参数,分别是 preState 与 action,一个标准写法如下:
1 | const initState = []; |
第一个参数是前一个状态,一般需要给一个初始值,建议在函数外定义,第二个参数是action,即动作;
一般的每个 action
动作都有一个type
与 data
,通过 type
判断来如何操作 state,函数的返回值就是加工完毕的 newState
2、需要创建store.js
文件,store
是一个实例对象是一个桥梁,
他暴露dispatch
函数给组件使用,自己接受reducer
函数,使用redux的标准函数createStore
可以创建
1 | import { legacy_createStore as createStore } from "redux"; |
3、订阅store,组件需要订阅 store,才能知道 store 中保存的 reducer
发生调用,
否则只是 dispatch(action)
,只会改变store中保存的状态,一般在组件挂载时订阅
1 | componentDIDMount(){ |
在不使用react-redux的情况下,store中状态更新时是不会通知组件变化的,因此需要手动订阅,然后调用 this.setState({})
,手动使类式组件刷新;
4、分配动作,调用store的dispatch
函数,传递 action 即可
2. 在函数式组件中使用react-redux的hooks
首先需要安装 react-redux :yarn add react-reudx
react-redux提供了两个关键的hooks函数:
useSelector
useDispatch
引入:import { useDispatch, useSelector } from'react-redux'
使用的方法与在类式组件中使用redux的前两部是类似的,但是在订阅与发布处是不同的;
1、暴露store对象
我们一般使用 React-redux 提供的<Provider>
组件包裹 App 组件,通过 store={store}
来全局暴露store对象
2、使用 useSelector
钩子函数来获取我们关注的状态
1 | const todos = useSelector(state => state) |
该函数接受的函数参数为reducer中指定的state,通过返回值暴露state中具体需要关注的属性。
此处因为我们只有一个 reducer 、也只有一个状态,所以返回的就是 state 本身,如果有多个 reducer 需要使用 combineReducers
函数来合并多个reducer ,代码如下:
1 | // 传给combineReducers函数的是一个对象我们在这里可以为各个 reducer 函数命名 |
当有多个reducer、一个reducer管理多个状态时,选择器的使用方式是这样:
state.<命名的reducer函数>.<reducer的状态中的某个状态>
例如 const selector = state => state.todos.xxxx
3、使用 useDispatch
钩子获得到 dispatch 函数对象
4、调用 dispatch()
传递 action 给 reducer
,当reducer
变更状态后无需手动刷新页面,
此时从 useSelector
中获取的状态对象就如真正的状态是一样的,可以自行更新渲染组件
3. 实操:使用redux改写todo
1 | import React from "react"; |
总结
上面的代码中的关键部分我都有注释,相比通过这个例子的对比,大家可以很好的理解状态管理的概念,与如何使用状态提升、redux 进行状态管理!