React全局状态管理的三种底层机制探究

 更新时间:2021年09月14日 10:11:56   作者:zxg_神说要有光  
近两年前端技术的发展如火如荼,大量的前端项目都在使用或转向 Vue 和 React 的阵营,由前端渲染页面的单页应用占比也越来越高,这篇文章主要给大家介绍了关于React全局状态管理的三种底层机制,需要的朋友可以参考下

前言

现代前端框架都是基于组件的方式来开发页面。按照逻辑关系把页面划分为不同的组件,分别开发不同的组件,然后把它们一层层组装起来,把根组件传入 ReactDOM.render 或者 vue 的 $mount 方法中,就会遍历整个组件树渲染成对应的 dom。

组件都支持传递一些参数来定制,也可以在内部保存一些交互状态,并且会在参数和状态变化以后自动的重新渲染对应部分的 dom。

虽然从逻辑上划分成了不同的组件,但它们都是同一个应用的不同部分,难免要彼此通信、配合。超过一层的组件之间如果通过参数通信,那么中间那层组件就要透传这些参数。而参数本来是为了定制组件用的,不应该为了通信而添加一些没意义的参数。

所以,对于组件的通信,一般不会通过组件参数的层层传递,而是通过放在全局的一个地方,双方都从那里存取的方式。

具体的用于全局状态管理的方案可能有很多,但是他们的底层无外乎三种机制:props、context、state。

下面,我们分别来探究一下这三种方式是如何做全局状态的存储和传递的。

props

我们可以通过一个全局对象来中转,一个组件向其中存放数据,另一个组件取出来的方式来通信。

组件里面写取 store 中数据的代码比较侵入式,总不能每个用到 store 的组件都加一段这些代码吧。我们可以把这些逻辑抽成高阶组件,用它来连接(connect)组件和 store。通过参数的方式来把数据注入到组件中,这样,对组件来说来源是透明的。

这就是 react-redux 做的事情:

import { connect } from 'react-redux';

function mapStateToProps(state) {
    return { todos: state.todos }
}
  
function mapDispatchToProps(dispatch) {
    return bindActionCreators({ addTodo }, dispatch)
}
  
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

此外,redux 还提供了中间件机制,可以拦截组件发送给 store 的 action 来执行一系列异步逻辑。

比较流行的中间件有 redux-thunk、redux-saga、redux-obervable,分别支持不同的方式来写组织异步流程,封装和复用异步逻辑。

类似的其他全局状态管理的库,比如 mobox、reconcil 等,也是通过 props 的方式注入全局的状态到组件中。

context

跨层组件通信一定要用第三方的方案么,不是的,react 本身也提供了 context 机制用于这种通信。

React.createContext 的 api 会返回 Provider 和 Consumer,分别用于提供 state 和取 state,而且也是通过 props 来透明的传入目标组件的。(这里的 Consumer 也可以换成 useContext 的 api,作用一样,class 组件用 Provider,function 组件用 useContext)

看起来和 redux 的方案基本没啥区别,其实最主要的区别是 context 没有执行异步逻辑的中间件。

所以 context 这种方案适合没有异步逻辑的那种全局数据通信,而 redux 适合组织复杂的异步逻辑。

案例代码如下:

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

不知道大家有没有想过,props、state 改变了,重新渲染组件很正常,context 改变了,又是怎么触发渲染的呢?

其实 react 内部做了处理,如果改变了 context 的值,那么会遍历所有的子组件,找到用到 context 值的组件,触发它的更新。

所以,props、state、context 都能够触发重新渲染。

state

redux 和 context 的方案,一个是第三方的,一个是内置的,都是通过 props 来传入值或者通过 hooks 来取值,但它们都是组件外部的,而 state 是组件内部的,怎么通过 state 来做全局状态共享呢?

其实 class 组件的 state 做不到,但是 function 组件的 state 可以,因为它是通过 useState 的 hooks api 创建的,而 useState 可以抽离到自定义 hooks 里,然后不同的 function 组件里引入来用。

import React, { useState } from 'react';

const useGlobalState = (initialValue) => {
    const [globalState, setGlobalState] = useState(initialValue);
    return [globalState, setGlobalState];
}

function ComponentA() {
    const [globalState, setGlobalState] = useGlobalState({name: 'aaa'});
    
    setGlobalState({name: bbb});
    return <div>{globalState}</div>
}

function ComponentA() {
    const [globalState, setGlobalState] = useGlobalState({name: 'aaa'});
 
    return <div>{globalState}</div>
}

上面这段代码可以共享全局状态?

确实不可以,因为现在每个组件都是在自己的 fiber.memorizedState 中放了一个新的对象,修改也是修改各自的。

那把这两个 useState 的初始值指向同一个对象不就行了?

这样多个组件之间就可以操作同一份数据了。

上面的代码要做下修改:

let globalVal  = {
    name: ''
}

const useGlobalState = () => {
    const [globalState, setGlobalState] = useState(globalVal);

    function updateGlobalState(val) {
        globalVal = val;
        setGlobalState(val);
    }

    return [globalState, updateGlobalState];
}

这样,每个组件创建的 state 都指向同一个对象,也能做到全局状态的共享。

但这里有个前提,就是只能修改对象的属性,而不能修改对象本身。

总结

现在前端页面的开发方式是把页面按照逻辑拆成一个个组件,分别开发每一个组件,然后层层组装起来,传入 ReactDOM.render 或者 Vue 的 $mount 来渲染。

组件可以通过 props 来定制,通过 state 来保存交互状态,这些变了都会自动的重新渲染。除此之外,context 变了也会找到用到 contxt 数据的子组件来触发重新渲染。

组件之间彼此配合,所以难免要通信,props 是用于定制组件的,不应该用来透传没意义的 props,所以要通过全局对象来中转。

react 本身提供了 context 的方案,createContext 会返回 Provider 和 Consumer,分别用来存放和读取数据。在 function 组件中,还可以用 useContext 来代替 Provider。

context 虽然可以共享全局状态,但是却没有异步逻辑的执行机制,当有复杂的异步逻辑的时候,还是得用 redux 这种,它提供了中间件机制用于组织异步流程、封装复用异步逻辑,比如 redux-saga 中可以把异步逻辑封装成 saga 来复用。
context 和 redux 都支持通过 props 来注入数据到组件中,这样对组件是透明的、无侵入的。

其实通过 useState 封装的 自定义 hooks 也可以通过把初始值指向同一个对象的方式来达到全局数据共享的目的,但是是有限制的,只能修改对象的属性,不能修改对象本身。其实用这种还不如用 context,只是提一下可以这样做。

简单总结一下就是:context 和 redux 都可以做全局状态管理,一个是内置的,一个是第三方的,没有异步逻辑用 context,有异步逻辑用 redux。

到此这篇关于React全局状态管理的三种底层机制的文章就介绍到这了,更多相关React全局状态管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • react-redux及redux状态管理工具使用详解

    react-redux及redux状态管理工具使用详解

    Redux是为javascript应用程序提供一个状态管理工具集中的管理react中多个组件的状态redux是专门作状态管理的js库(不是react插件库可以用在其他js框架中例如vue,但是基本用在react中),这篇文章主要介绍了react-redux及redux状态管理工具使用详解,需要的朋友可以参考下
    2023-01-01
  • 详解React Fiber的工作原理

    详解React Fiber的工作原理

    这篇文章主要介绍了React Fiber的工作原理的相关资料,帮助大家更好的理解和学习使用React框架,感兴趣的朋友可以了解下
    2021-04-04
  • 详解如何在项目中使用jest测试react native组件

    详解如何在项目中使用jest测试react native组件

    本篇文章主要介绍了详解如何在项目中使用jest测试react native组件,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • React+Electron快速创建并打包成桌面应用的实例代码

    React+Electron快速创建并打包成桌面应用的实例代码

    这篇文章主要介绍了React+Electron快速创建并打包成桌面应用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • React实现分页效果

    React实现分页效果

    这篇文章主要为大家详细介绍了React实现分页效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • React如何解决样式污染问题

    React如何解决样式污染问题

    这篇文章主要介绍了React如何解决样式污染问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • react-native滑动吸顶效果的实现过程

    react-native滑动吸顶效果的实现过程

    这篇文章主要给大家介绍了关于react-native滑动吸顶效果的实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用react-native具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • Create react app修改webapck配置导入文件alias

    Create react app修改webapck配置导入文件alias

    这篇文章主要为大家介绍了Create react app修改webapck配置导入文件alias,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • React路由封装的实现浅析

    React路由封装的实现浅析

    路由是React项目中相当重要的概念,对于功能较为复杂的网页来说,必然会涉及到不同功能间的页面跳转,本篇文章将对React官方维护的路由库React-Router-Dom的使用和常用组件进行讲解,同时对路由组件传递param参数的方式进行讲解,希望对各位读者有所参考
    2022-08-08
  • react-native fetch的具体使用方法

    react-native fetch的具体使用方法

    本篇文章主要介绍了react-native fetch的具体使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11

最新评论