前言

现代前端框架都是基于组件的方式来开发页面。按照逻辑关系把页面划分为不同的组件,分别开发不同的组件,然后把它们一层层组装起来,把根组件传入 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全局状态管理内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

React全局状态管理的三种底层机制探究的更多相关文章

  1. ios – React native链接到另一个应用程序

    如果是错误的,有人知道如何调用正确的吗?

  2. ios – React Native – 在异步操作后导航

    我正在使用ReactNative和Redux开发移动应用程序,我正面临着软件设计问题.我想调用RESTAPI进行登录,如果该操作成功,则导航到主视图.我正在使用redux和thunk所以我已经实现了异步操作,所以我的主要疑问是:我应该把逻辑导航到主视图?我可以直接从动作访问导航器对象并在那里执行导航吗?.我对组件中的逻辑没有信心.似乎不是一个好习惯.有没有其他方法可以做到这一点?

  3. 在ios中使用带有React Native(0.43.4)的cocoapods的正确方法是什么?

    我已经挖掘了很多帖子试图使用cocoapods为本地ios库设置一个反应原生项目,但我不可避免地在#import中找到了丢失文件的错误.我的AppDelegate.m文件中的语句.什么是使用反应原生的可可豆荚的正确方法?在这篇文章发表时,我目前的RN版本是0.43.4,而我正在使用Xcode8.2.1.这是我的过程,好奇我可能会出错:1)

  4. ios – React Native WebView滚动行为无法按预期工作

    如何确保滚动事件的行为与ReactNative应用程序中的浏览器相同?

  5. ios – React Native – BVLinearGradient – 找不到’React/RCTViewManager.h’文件

    谢谢.解决方法几天前我遇到了完全相同的问题.问题是在构建应用程序时React尚未链接.试试这个:转到Product=>Scheme=>管理方案…=>点击你的应用程序Scheme,然后点击Edit=>转到Build选项卡=>取消选中ParallelizeBuild然后点击标志添加目标=>搜索React,选择第一个名为React的目标,然后单击Add然后在目标列表中选择React并将其向上拖动到该列表中的第一个.然后转到Product=>再次清理并构建项目.这应该有所帮助.

  6. ios – React Native – NSNumber无法转换为NSString

    解决方法在你的fontWeight()函数中也许变成:

  7. ios – React native error – react-native-xcode.sh:line 45:react-native:command not found命令/ bin/sh失败,退出代码127

    尝试构建任何(新的或旧的)项目时出现此错误.我的节点是版本4.2.1,react-native是版本0.1.7.我看过其他有相同问题的人,所以我已经更新了本机的最新版本,但是我仍然无法通过xcode构建任何项目.解决方法要解决此问题,请使用以下步骤:>使用节点版本v4.2.1>cd进入[你的应用]/node_modules/react-native/packager>$sh./packager.s

  8. 反应原生 – 如何通过Xcode构建React Native iOS应用程序到设备?

    我试图将AwesomeProject应用程序构建到设备上.构建成功并启动屏幕显示,但后来我看到一个红色的“无法连接到开发服务器”屏幕.它表示“确保节点服务器正在运行–从Reactroot运行”npmstart“.看起来节点服务器已经运行,因为当我做npm启动时,我收到一个EADDRINUSE消息,表示该端口已经在使用.解决方法从设备访问开发服务器您可以使用开发服务器快速迭代设备.要做到这一点,你的

  9. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  10. 如何为iOS的React Native设置分析

    所以我已经完成了一个针对iOS的ReactNative项目,但是我想在其中分析.我尝试了react-native-google-analytics软件包,但是问题阻止了它的正常工作.此外,react-native-cordova-plugin软件包只适用于Android,因此插入Cordova插件进行分析的能力现在已成为问题.我也没有Swift/ObjectiveC的经验,所以将完全失去GA的插入.有没有人有任何建议如何连接GoogleAnalytics的ReactNativeforiOS?

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部