We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 是一个跨平台的库,可以用于构建 Web 应用、移动应用(React Native)等。而 react-dom 就是 React 在 Web 环境中的渲染实现,用于将 React 组件渲染到实际的 DOM 上,并提供了一些与 DOM 操作相关的功能。
react-dom
之前我们在 react-reconciler/src/hostConfig.ts 中模拟实现了一些生成、插入 DOM 元素的函数,现在就在 react-dom 中真正实现它。
react-reconciler/src/hostConfig.ts
先创建 packages/react-dom 文件夹,并初始化:
packages/react-dom
cd packages mkdir react-dom cd react-dom pnpm init
初始化的 package.json文件如下所示:
package.json
// packages/react-dom/package.json { "name": "react-dom", "version": "1.0.0", "description": "", "module": "index.ts", "dependencies": { "shared": "workspace: *", "react-reconciler": "workspace: *" }, "peerDependencies": { "react": "workspace: *" }, "keywords": [], "author": "", "license": "ISC" }
新建 packages/react-dom/scr/hostConfig.ts 文件,将之前的 hostConfig.ts 文件复制过来并删除:
packages/react-dom/scr/hostConfig.ts
hostConfig.ts
// packages/react-dom/scr/hostConfig.ts export type Container = Element; export type Instance = Element; export const createInstance = (type: string, porps: any): Instance => { // TODO: 处理 props const element = document.createElement(type); return element; }; export const appendInitialChild = ( parent: Instance | Container, child: Instance ) => { parent.appendChild(child); }; export const createTextInstance = (content: string) => { const element = document.createTextNode(content); return element; }; export const appendChildToContainer = ( child: Instance, parent: Instance | Container ) => { parent.appendChild(child); };
接着实现 packages/react-dom/scr/root.ts,先来实现 ReactDOM.createRoot().render() 方法,我们之前讲过,这个函数过程中会调用两个 API:
packages/react-dom/scr/root.ts
ReactDOM.createRoot().render()
element
这两个 API 在 react-reconciler 包里面已经实现了,直接调用即可。
react-reconciler
import { createContainer, updateContainer } from 'react-reconciler/src/fiberReconciler'; import { Container } from './hostConfig'; import { ReactElementType } from 'shared/ReactTypes'; // 实现 ReactDOM.createRoot(root).render(<App />); export function createRoot(container: Container) { const root = createContainer(container); return { render(element: ReactElementType) { updateContainer(element, root); } }; }
现在我们已经实现了 React 首屏渲染的更新流程,即:
通过 ReactDOM.createRoot(root).render(<App />) 方法,创建 React 应用的根节点,将一个 Placement 加入到更新队列中,并触发了首屏渲染的更新流程:在对 Fiber 树进行深度优先遍历(DFS)的过程中,比较新旧节点,生成更新计划,执行 DOM 操作,最终将 <App /> 渲染到根节点上。
ReactDOM.createRoot(root).render(<App />)
Placement
<App />
目前我们还只实现了首屏渲染触发更新,还有很多触发更新的方式,如类组件的 this.setState()、函数组件的 useState useEffect ,将在后面实现。
this.setState()
useState useEffect
接着来实现 react-dom 包的打包流程,具体过程参考 第 2 节,需要注意两点:
hostConfig
pnpm i -D -w @rollup/plugin-alias
ReactDOM = Reconciler + hostConfig
react-dom.config.js 的具体配置如下:
react-dom.config.js
// scripts/rollup/react-dom.config.js import { getPackageJSON, resolvePkgPath, getBaseRollupPlugins } from './utils'; import generatePackageJson from 'rollup-plugin-generate-package-json'; import alias from '@rollup/plugin-alias'; const { name, module, peerDependencies } = getPackageJSON('react-dom'); // react-dom 包的路径 const pkgPath = resolvePkgPath(name); // react-dom 包的产物路径 const pkgDistPath = resolvePkgPath(name, true); export default [ // react-dom { input: `${pkgPath}/${module}`, output: [ { file: `${pkgDistPath}/index.js`, name: 'ReactDOM', format: 'umd' }, { file: `${pkgDistPath}/client.js`, name: 'client', format: 'umd' } ], external: [...Object.keys(peerDependencies)], plugins: [ ...getBaseRollupPlugins(), // webpack resolve alias alias({ entries: { hostConfig: `${pkgPath}/src/hostConfig.ts` } }), generatePackageJson({ inputFolder: pkgPath, outputFolder: pkgDistPath, baseContents: ({ name, description, version }) => ({ name, description, version, peerDependencies: { react: version }, main: 'index.js' }) }) ] }, ];
再将 tsconfig.json 中的 hostConfig 指向 react-dom 包中的路径;
tsconfig.json
// tsconfig.json { // ... "paths": { "hostConfig": ["./react-dom/src/hostConfig.ts"] } }
最后,为了在执行 npm run build-dev 时能同时将 react 和 react-dom 都打包,我们新建一个 dev.config.js 文件,将 react.config.js 和 react-dom.config.js 统一导出。
npm run build-dev
react
dev.config.js
react.config.js
// scripts/rollup/dev.config.js import reactDomConfig from './react-dom.config'; import reactConfig from './react.config'; export default [...reactConfig, ...reactDomConfig];
并将 package.json 中的 npm run build-dev 命令改为:"rimraf dist && rollup --config scripts/rollup/dev.config.js --bundleConfigAsCjs"。
"rimraf dist && rollup --config scripts/rollup/dev.config.js --bundleConfigAsCjs"
现在运行 npm run build-dev 就可以得到 react 和 react-dom 的打包产物了。通过 pnpm lint --global 或者 npm run demo 可在测试项目中运行你自己开发的 react 包和 react-dom 包。
pnpm lint --global
npm run demo
至此,我们就实现了基础版的 react-dom 包,更多的功能我们将在后面一一实现。
相关代码可在 git tag v1.7 查看,地址:https://github.com/2xiao/my-react/tree/v1.7
git tag v1.7
The text was updated successfully, but these errors were encountered:
No branches or pull requests
React 是一个跨平台的库,可以用于构建 Web 应用、移动应用(React Native)等。而
react-dom
就是 React 在 Web 环境中的渲染实现,用于将 React 组件渲染到实际的 DOM 上,并提供了一些与 DOM 操作相关的功能。之前我们在
react-reconciler/src/hostConfig.ts
中模拟实现了一些生成、插入 DOM 元素的函数,现在就在react-dom
中真正实现它。1. 实现 react-dom 包
先创建
packages/react-dom
文件夹,并初始化:初始化的
package.json
文件如下所示:新建
packages/react-dom/scr/hostConfig.ts
文件,将之前的hostConfig.ts
文件复制过来并删除:接着实现
packages/react-dom/scr/root.ts
,先来实现ReactDOM.createRoot().render()
方法,我们之前讲过,这个函数过程中会调用两个 API:element
)渲染到容器中,并更新整个应用的状态。这两个 API 在
react-reconciler
包里面已经实现了,直接调用即可。现在我们已经实现了 React 首屏渲染的更新流程,即:
通过
ReactDOM.createRoot(root).render(<App />)
方法,创建 React 应用的根节点,将一个Placement
加入到更新队列中,并触发了首屏渲染的更新流程:在对 Fiber 树进行深度优先遍历(DFS)的过程中,比较新旧节点,生成更新计划,执行 DOM 操作,最终将<App />
渲染到根节点上。目前我们还只实现了首屏渲染触发更新,还有很多触发更新的方式,如类组件的
this.setState()
、函数组件的useState useEffect
,将在后面实现。2. 实现打包流程
接着来实现
react-dom
包的打包流程,具体过程参考 第 2 节,需要注意两点:hostConfig
的导入路径:pnpm i -D -w @rollup/plugin-alias
;ReactDOM = Reconciler + hostConfig
,不要将 react 包打包进 react-dom 里,否则会出现数据共享冲突;react-dom.config.js
的具体配置如下:再将
tsconfig.json
中的hostConfig
指向react-dom
包中的路径;最后,为了在执行
npm run build-dev
时能同时将react
和react-dom
都打包,我们新建一个dev.config.js
文件,将react.config.js
和react-dom.config.js
统一导出。并将
package.json
中的npm run build-dev
命令改为:"rimraf dist && rollup --config scripts/rollup/dev.config.js --bundleConfigAsCjs"
。现在运行
npm run build-dev
就可以得到react
和react-dom
的打包产物了。通过pnpm lint --global
或者npm run demo
可在测试项目中运行你自己开发的react
包和react-dom
包。至此,我们就实现了基础版的
react-dom
包,更多的功能我们将在后面一一实现。相关代码可在
git tag v1.7
查看,地址:https://github.com/2xiao/my-react/tree/v1.7The text was updated successfully, but these errors were encountered: