此时想要改变 state 的值,需要改写 ReactDOM.render() 方法,需要用单例来模拟全局变量,而不能每次都创建新的 instance。
const ReactDOM = {
render: (vdom, container) => {
let type = vdom.type
let props = vdom.props || []
let children = props && props.children || []
let element = null
// 1.创建元素:文本节点和普通节点需用不同方式创建
if (type === 'text') {
element = document.createTextNode(vdom.props.nodeValue)
} else if (isClass(type)) {
// type 可能是 Component 类的子类
let cls = type
if (cls.instance === undefined) {
cls.instance = new cls(props)
}
let instance = cls.instance
// 改变 state 的值
let state = instance.state
let r = instance.render(state, props)
element = ReactDOM.render(r, container)
} ...
}
}
至此,我们便实现了简易版 React,可以解析 JSX 模版,创建 DOM 元素并添加到 html 页面中,添加点击事件,state 数据发生变化时刷新视图。
simple-react
拆解实现简易版 React
React 基础原理
新建一个 js 文件,以实现简易版 React。假设要将以下 JSX 显示在 html 页面上:
为了便于理解,我们先把属性单独处理。真实的 React vdom 只包含 type 和 properties,其他内容都放在 properties 里。(先手动解析用于测试,后续实现 React.createElement()方法用于创建对象):
新建一个程序主入口 __main,根据 React 的用法可知,我们需要调用 ReactDOM.render() 方法,解析 JSX 并生成的 DOM 节点,然后添加到 html 页面对应的容器中。
针对上述结构的 vdom,可以实现如下 ReactDOM.render() 方法。
至此,我们了解了 React 的基本原理:解析 JSX 模版,并生成 DOM 节点插入到 html 页面的容器中。 接下来,实现 React.createElement(type, props, children) 方法。根据 React 官网的示例可知,JSX 模版等效于由 React.createElement() 方法创建的对象。
可见 children 等其他属性包含在 props 中(本次简易版 React 只考虑 type 和 props)。将上述 vdomButton 改写成调用 React.createElement() 创建对象:
由创建的对象结构可知,React.createElement() 可简单处理为:
由于 vdom 结构发生变化,我们需要修改 ReactDOM.render() 方法。
这样我们就可以在 html 页面创建一个按钮元素。
使用 Babel 转译 JSX
在 React 中是用 Babel 把 JSX 转译成一个名为 React.createElement() 函数调用。我们也用 Babel 试试看。 先建一个 package.json 文件,然后安装所需的 Babel 依赖。
module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'build'), }, module: { rules: [ { test: /.js$/, // 匹配 .js 和 .jsx 文件, use: { loader: 'babel-loader', } } ] }, mode: 'development', watch: true, // 加上监听,不需要手动刷新 }
在 package.json 文件中加入指令,便于打包运行。
此时程序能正常运行,但是当解析到文本节点时会报错。原因是当 children 是文本时,要单独创建文本节点。需要改写一下 createElement() 方法。
现在给元素绑定点击事件。
至此,我们已经实现了添加元素并绑定事件。接下来处理数据变化时自动更新元素。 假设要实现能加减数字的按钮组合。
此时需要调用 Component 类的 setState() 方法,实现页面刷新(简易版 React 只实现整个页面刷新,不考虑 Diff 局部刷新情况)。
由于要把 vdom 和 element 存储到 store 中,在调用 ReactDOM.render() 时就要做判断,将变量存起来。
此时想要改变 state 的值,需要改写 ReactDOM.render() 方法,需要用单例来模拟全局变量,而不能每次都创建新的 instance。
至此,我们便实现了简易版 React,可以解析 JSX 模版,创建 DOM 元素并添加到 html 页面中,添加点击事件,state 数据发生变化时刷新视图。