背景介绍
前一段时间,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
/**
* 注意这是重点,这里导入了 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
/**
* 注意这里是重点,这里导入的对象用于 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
// 消费 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/