千家信息网

如何优雅的使用react hooks来进行状态管理

发表于:2025-01-23 作者:千家信息网编辑
千家信息网最后更新 2025年01月23日,在使用react和redux的过程中,一直有一个问题,哪些状态需要放在redux中,状态需要保存在组件内的local state中,此外不合理的使用redux可能会带来状态管理混乱的问题,此外对于lo
千家信息网最后更新 2025年01月23日如何优雅的使用react hooks来进行状态管理

在使用react和redux的过程中,一直有一个问题,哪些状态需要放在redux中,状态需要保存在组件内的local state中,此外不合理的使用redux可能会带来状态管理混乱的问题,此外对于local state局部状态而言,react hooks提供了一个比class中的setState更好的一个替代方案。本文主要从状态管理出发,讲讲如何优雅的使用hooks来进行状态管理。

  • 如何使用redux

  • react hooks管理local state

  • react hooks如何解决组件间的通信

原文在我的博客中:github.com/fortheallli… 欢迎订阅


一、如何使用redux

首先要明确为什么要使用redux,这一点很重要,如果不知道为什么使用redux,那么在开发的过程中肯定不能合理的使用redux.首先来看redux的本质:

redux做为一款状态管理工具,主要是为了解决组件间通信的问题。

既然是组件间的通信问题,那么显然将所有页面的状态都放入redux中,是不合理的,复杂度也很高。

(1)全量使用redux

笔者在早期也犯了这个问题,在应用中,不管什么状态,按页面级路由拆分,全部放在redux中,页面任何状态的更改,通过react-redux的mapState和mapDispatch来实现。


redux中的状态从状态更新到反馈到视图,是一个过程链太长,从dispatch一个action出发,然后走reducer等逻辑,一个完整的链路包含:

创建action,创建redux中间件,创建相应type的reducer函数,创建mapState和mapDispatch等。

如果将所有状态都保存在redux中,那么每一个状态必须走这几步流程,及其繁琐,毫无疑问增加了代码量

(2)减少局部状态和redux状态的不合理混用

全量使用redux的复杂度很高,我们当然考虑将一部分状态放在redux中,一部分状态放在local state中,但是这种情况下,很容易产生一个问题,就是如果local State跟redux中的state存在状态依赖。

举例来说,在redux中的状态中有10个学生

 //redux  students = [{name:"小白",score:70},{name:"小红",score:50}....]复制代码

在local state中我们保存了分数在60分以上的学生

 // local state  state = [{name:"小白",score:70}]复制代码

如果redux中的学生改变了,我们需要从redux中动态的获取students信息,然后改变局部的state.结合react-redux,我们需要在容器组件中使用componentWillReceivedProps或者getDerivedStateFromProps这个声明周期,来根据props改变局部的local state.

componentWillReceivedProps这里不讨论,为了更高的安全性,在react中用静态的getDerivedStateFromProps代替了componentWillReceivedProps这里不讨论,而getDerivedStateFromProps这个声明周期函数在props和state变化的时候都会去执行,因此如果我们需要仅仅在props的改变而改变局部的local state,在这个声明周期中会存在着很复杂的判断逻辑。

redux中的状态和local state中的状态相关联的越多,getDerivedStateFromProps这个声明周期函数就越复杂

给我们的启示就是尽可能的减少getDerivedStateFromProps的使用,如果实在是redux和local state有关联性,用id会比直接用对象或者数组好,比如上述的例子,我们可以将学生分组,并给一个组号,每次在redux中的学生信息发生改变的时候会改变相应的组号。 这样在getDerivedStateFromProps只需要判断组号是否改变即可:

 class Container extends React.Component{ state = { group_id:number }  static getDerivedStateFromProps(props,state){ if(props.group_id!==state.group_id){  ... 更新及格的学生 }else{ return null } } }复制代码

这里推荐https://github.com/paularmstrong/normalizr,如果实在redux和local state关联性强,可以先将数据范式化,范式化后的数据类似于给一个复杂结构一个id,这样子会简化getDerivedStateFromProps的逻辑.

(3)本节小结

如何使用redux,必须从redux的本质出发,redux的本质是为了解决组件间的通信问题,因此组件内部独有的状态不应该放在redux中,此外如果redux结合class类组件使用,可以将数据范式化,简化复杂的判断逻辑。

二、react hooks管理local state

前面将了应该如何使用redux,那么如何维护local state呢,React16.8中正式增加了hooks。通过hooks管理local state,简单易用可扩展。

在hooks中的局部状态常见的有3种,分别是useState、useRef和useReducer

(1) useState

useState是hooks中最常见的局部状态,比如:

 const [hide, setHide] = React.useState(false); const [name, setName] = React.useState('BI');复制代码

理解useState必须明确,在react hooks中:

每一次渲染都有它自己的 Props and State

一个经典的例子就是:

 function Counter() { const [count, setCount] = useState(0);  function handleAlertClick() { setTimeout(() => { alert('You clicked on: ' + count); }, 3000); }  return ( 

You clicked {count} times

); }复制代码

如果我按照下面的步骤去操作:

  • 点击增加counter到3

  • 点击一下 "Show alert"

  • 点击增加 counter到5并且在定时器回调触发前完成

猜猜看会alert出什么?


结果是弹出了3,alert会"捕获"我点击按钮时候的状态,也就是说每一次的渲染都会有独立的props和state.

(2) useRef

在react hooks中,我们知道了每一次的渲染都会有独立的props和state,那么如果我们需要跟类组件一样,每次都能拿到最新的渲染值时,应该怎么做呢?此时我们可以用useRef

useRef提供了一个Mutable可变的数据

我们来修改上述的例子,来是的alert为5:

 function Counter() { const [count, setCount] = useState(0) const late = useRef(0) function handleAlertClick() { setTimeout(() => { alert('You clicked on: ' + late.current) }, 3000) } useEffect(() => { late.current = count }) return ( 

You clicked {count} times

) }复制代码

如此修改以后就不是alert3 而是弹出5

(3) useReducer

react hooks中也提供了useReducer来管理局部状态.

当你想更新一个状态,并且这个状态更新依赖于另一个状态的值时,你可能需要用useReducer去替换它们。

同样的用例子来说明:

 function Counter() { const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state;  useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]);  return ( <> 

{count}

{ dispatch({ type: 'step', step: Number(e.target.value) }); }} /> ); } const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') { return { count: count + step, step }; } else if (action.type === 'step') { return { count, step: action.step }; } else { throw new Error(); } }复制代码

解释上面的结果主要来看useEffect部分:

 useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]);复制代码

在state中的count依赖与step,但是使用了useReducer后,我们不需要在useEffect的依赖变动数组中使用step,转而用dispatch来替代,这样的好处就是减少不必要的渲染行为.

此外:局部状态不推荐使用 useReducer ,会导致函数内部状态过于复杂,难以阅读。 useReducer 建议在多组件间通信时,结合 useContext 一起使用。

三、react hooks如何解决组件间的通信

react hooks中的局部状态管理相比于类组件而言更加简介,那么如果我们组件采用react hooks,那么如何解决组件间的通信问题。

###(1) UseContext

最基础的想法可能就是通过useContext来解决组件间的通信问题。

比如:

function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment }}let Counter = createContext(null)function CounterDisplay() { let counter = useContext(Counter) return ( 

You clicked {counter.count} times

)}function App() { let counter = useCounter() return ( )}复制代码

在这个例子中通过createContext和useContext,可以在App的子组件CounterDisplay中使用context,从而实现一定意义上的组件通信。

此外,在useContext的基础上,为了其整体性,业界也有几个比较简单的封装:

github.com/jamiebuilds… github.com/diegohaz/co…

但是其本质都没有解决一个问题:

如果context太多,那么如何维护这些context

也就是说在大量组件通信的场景下,用context进行组件通信代码的可读性很差。这个类组件的场景一致,context不是一个新的东西,虽然用了useContext减少了context的使用复杂度。

###(2) Redux结合hooks来实现组件间的通信

hooks组件间的通信,同样可以使用redux来实现。也就是说:

在React hooks中,redux也有其存在的意义

在hooks中存在一个问题,因为不存在类似于react-redux中connect这个高阶组件,来传递mapState和mapDispatch, 解决的方式是通过redux-react-hook或者react-redux的7.1 hooks版本来使用。

  • redux-react-hook

在redux-react-hook中提供了StoreContext、useDispatch和useMappedState来操作redux中的store,比如定义mapState和mapDispatch的方式为:

import {StoreContext} from 'redux-react-hook';ReactDOM.render(   , document.getElementById('root'),);import {useDispatch, useMappedState} from 'redux-react-hook';export function DeleteButton({index}) { // Declare your memoized mapState function const mapState = useCallback( state => ({ canDelete: state.todos[index].canDelete, name: state.todos[index].name, }), [index], ); // Get data from and subscribe to the store const {canDelete, name} = useMappedState(mapState); // Create actions const dispatch = useDispatch(); const deleteTodo = useCallback( () => dispatch({ type: 'delete todo', index, }), [index], ); return (  );}复制代码
  • react-redux 7.1的hooks版

这也是官方较为推荐的,react-redux 的hooks版本提供了useSelector()、useDispatch()、useStore()这3个主要方法,分别对应与mapState、mapDispatch以及直接拿到redux中store的实例.

简单介绍一下useSelector,在useSelector中除了能从store中拿到state以外,还支持深度比较的功能,如果相应的state前后没有改变,就不会去重新的计算.

举例来说,最基础的用法:

import React from 'react'import { useSelector } from 'react-redux'export const TodoListItem = props => { const todo = useSelector(state => state.todos[props.id]) return 
{todo.text}
}复制代码

实现缓存功能的用法:

import React from 'react'import { useSelector } from 'react-redux'import { createSelector } from 'reselect'const selectNumOfDoneTodos = createSelector( state => state.todos, todos => todos.filter(todo => todo.isDone).length)export const DoneTodosCounter = () => { const NumOfDoneTodos = useSelector(selectNumOfDoneTodos) return 
{NumOfDoneTodos}
}export const App = () => { return ( <> Number of done todos: )}复制代码

在上述的缓存用法中,只要todos.filter(todo => todo.isDone).length不改变,就不会去重新计算.

四、总结

react中完整的状态管理分为全局状态和局部状态,而react hooks简化了局部状态,使得管理局部状态以及控制局部渲染极其方便,但是react hooks本质上还是一个视图组件层的,并没有完美的解决组件间的通信问题,也就是说,redux等状态管理机和react hooks本质上并不矛盾。

在我的实践中,用redux实现组件间的通信而react hooks来实现局部的状态管理,使得代码简单已读的同时,也减少了很多不必要的redux样板代码.


状态 组件 代码 局部 通信 管理 问题 复杂 学生 本质 例子 就是 也就是 也就是说 函数 周期 数据 逻辑 更新 不合理 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 银行业近五年网络安全宣传 软件开发工作室有哪些岗位 软件开发岗位描述 贤相软件开发 湖北数字化城管软件开发 开展网络安全教育作文 算网络安全技术的身份验证 望远县软件开发技术好不好 锡山区环保网络技术收购价格 厦门备件管理软件开发 登陆失败服务器未响应 软件开发公司要求驻点的目的 苏州防爆刀片服务器供应 做软件开发15k多么 杭州美迪网络技术有限公司 神州信息数据库 娃娃网络技术有限公司 数据库格式选择 香港服务器租用有哪些好处 数据库表是独立文件吗 数据库说未提交行1中的数据 地产软件开发联系方式 国内cae软件开发企业排名 佛山pc软件开发公司 反传销和网络安全教育的内容 国服lol服务器代理 计算机网络安全一般包括 完成服务器配置完整步骤 士官院校信息网络技术热门吗 一级网络安全素质教育考试软件
0