尝试 React 17 RC / Demo of Gradual React Upgrades

2022-10-28 13:37:37 浏览数 (2)

背景介绍

前一段时间,React团队发布了 React 17 RC [1],对于这个版本,官方说的是没有新特性,可以称作是一个 “垫脚石” 版本,为以后的版本更新做准备。主要是因为之前的 “all-or-nothing” 升级策略遇到了问题:一方面React团队要一直维护老旧的并且使用较少的API;一方面开发者在面对React版本升级时,往往需要升级整个项目,这意味较高的风险,特别对于很老旧的项目(哈哈,估计到时候很多人都会吐槽~)。所以提供了一个 渐进升级 的方案,那 React 17 就是使得 渐进升级 变得更加容易!为此还更改了 React 的事件代理模式。这篇文章是对官方提供的 渐进升级 的例子 Demo of Gradual React Upgrades [2],表述一下自己认为它是如何工作的。

Part 0

首先从目录说起,src目录下:主要目录有三个

  • modern:使用新版本的React,并且具有独立的 package.json 用于安装 modern 下需要的依赖;
  • legacy:使用老版本的React,并且具有独立的 package.json 用于安装 legacy 下需要的依赖;
  • shared:modern 和 legacy 都使用的组件、context、hooks,格外注意的是 shared 里面的内容会被分别复制到 modern、legacy 的 shared 目录中。
  • 外层的文件中除了 index.js 是 入口文件,还有一个 store.js 存放 redux 中的 store。

那么对于新建的文件或者目录应该怎么存放呢?其实从目录的划分就可以看出,要将项目中使用新版本 React 的部分(modern)和老版本 React (legacy)的部分隔离开,所以呢只在 modern 中使用的放入 modern,legacy 同理;modern 和 legacy 都需要使用的放入 shared;对于一些不涉及到 React 版本的内容直接放入 src 根目录即可。

part 1

这个例子中 React tree 的嵌套模式是新版本嵌套老版本,实现了 context、react-router、redux 的共用,那么如何实现上述三者的共用呢?其实按照正常的思维当然是都来源于一个即可,没错这个例子就是这样实现的。主要涉及的文件为(主要关注点在于如何实现共用的代码,其余的部分不做说明):

  • modern / lazyLegacyRoot.js
代码语言:javascript复制
/** 
* 注意这是重点,这里导入了 Themecontext,react-router、react-redux 的 context
* context 中存放的就各自的对象信息
*/
import {__RouterContext} from 'react-router';
import {ReactReduxContext} from 'react-redux';
import ThemeContext from './shared/ThemeContext';

// 懒加载 legacy root component
const createLegacyRoot = readModule(rendererModule, () =>
 import('../legacy/createLegacyRoot')
).default;
// 懒加载 legacy children component
const Component = readModule(componentModule, getLegacyComponent).default;
const containerRef = useRef(null);
const rootRef = useRef(null);
// Populate every contexts we want the legacy subtree to see.
// Then in src/legacy/createLegacyRoot we will apply them.
// 获取每个 context 里的信息
const theme = useContext(ThemeContext);
const router = useContext(__RouterContext);
const reactRedux = useContext(ReactReduxContext);
/**
* 组成一个 context 对象,拥有 theme、router、reactRedux 属性
* 并分别赋值为上述的 context 信息
*/
const context = useMemo(
  () => ({
    theme,
    router,
    reactRedux,
  }),[theme, router, reactRedux]);
  
// Create/unmount.
useLayoutEffect(() => {
  if (!rootRef.current) {
    rootRef.current = createLegacyRoot(containerRef.current);
  }
const root = rootRef.current;
  return () => {
    root.unmount();
  };
}, [createLegacyRoot]);

// Mount/update.
useLayoutEffect(() => {
  if (rootRef.current) {
    // 将 context 注入到 需要使用 context、react-router、react-redux 的组件中去
    rootRef.current.render(Component, props, context);
  }
}, [Component, props, context]);
  • legacy / createLegacyRoot.js
代码语言:javascript复制
/**
* 注意这里是重点,这里导入的对象用于 Provider,将 context 注入到被包裹的组件中
* 这样在组件中就可以使用 ThemeContext、react-router、React-Redux
*/
import ThemeContext from './shared/ThemeContext';
// Note: this is a semi-private API, but it's ok to use it
// if we never inspect the values, and only pass them through.
import {__RouterContext} from 'react-router';
import {Provider} from 'react-redux';

// 进行 Provider 注册 context
<ThemeContext.Provider value={context.theme}>
    <__RouterContext.Provider value={context.router}>
    {/*
      If we used the newer react-redux@7.x in the legacy/package.json,
      we woud instead import {ReactReduxContext} from 'react-redux'
      and render <ReactReduxContext.Provider value={context.reactRedux}>.
    */}
    <Provider store={context.reactRedux.store}>{children}</Provider>
  </__RouterContext.Provider>
</ThemeContext.Provider>

上述代码说明了如何实现 context、react-router、react-redux 的共用,最核心的方式就是使用 Provider 注册 context,让我比较疑惑的是 react-router、react-redux 竟然也有 context,猜测它们内部实现就用到了 context。

part 2

好了,上面既然 Provider 了各自的 context,那么就可以使用了:

  • legacy / Greeting.js
代码语言:javascript复制
// 消费 ThemeContext
<ThemeContext.Consumer>
  {theme => (
    <div style={{border: '1px dashed black', padding: 20}}>
      <h3>src/legacy/Greeting.js</h3>

      <h4 style={{color: theme}}>
        This component is rendered by the nested React ({React.version}).
       </h4>
       <Clock />
        <p>
          Counter: {this.props.counter}{' '}
          <button onClick={() => this.props.dispatch({type: 'increment'})}>
             
          </button>
        </p>
        <b>
          <Link to="/">Go to Home</Link>
        </b>           
      </div>
  )}
 </ThemeContext.Consumer>

总结

通过这个例子,大致了解了项目渐进迁移所使用的方式,可以为以后的项目迁移作准备,上面写的正是我理解时间最长的部分,我认为也是关键的部分,虽然有些简单。更多具体信息请查看 参考链接。

参考链接

[1]:https://reactjs.org/blog/2020/08/10/react-v17-rc.html

[2]:https://github.com/reactjs/react-gradual-upgrade-demo/

0 人点赞