最近在学习 “winter 手把手带你实现 ToyReact ”课程,这是第一节课的学习报告心得,记录一下学习历程
ToyReact顾名思义即实现一个玩具react,也就是一个极简react,通过这个极简react来了解react框架的内部原理
1.准备工作
需要提前安装好nodejs和npm环境,然后npm init -y然后安装"webpack"、"webpack-cli"、"@babel/core(把高级别es版本翻译成es5)"、"@babel/plugin-transform-react-jsx(将html结构转成jsx格式)"、 "@babel/preset-env"
2.代码编写
新建一个目录在目录中分别新建webpack.config.js toy-react.js main.js
webpack.config.js
@babel/plugin-transform-react-jsx做了什么呢? 如在js代码中写了一个这样的函数
输入
const profile = (
<div>
<img src="avatar.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>
);
输出
const profile = React.createElement("div", null,
React.createElement("img", { src: "avatar.png", className: "profile" }),
React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
如果需要将React.createElement自定义可以将pragma参数改成如:'createElement'这样的话上面的输出会变成
const profile = createElement("div", null,
createElement("img", { src: "avatar.png", className: "profile" }),
createElement("h3", null, [user.firstName, user.lastName].join(" "))
);
webpack配置如下:
module.exports = {
entry: {
main: './main.js'
},
module: {
rules: [
{
test:/\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [['@babel/plugin-transform-react-jsx',{pragma: 'createElement'}]]
}
}
}
]
},
mode: 'development',
optimization: {
minimize: false
}
}
main.js
import { createElement, Component, render } from './toy-react'
class MyComponent extends Component {
render(){
return <div>
<p>MyComponent</p>
{this.children}
</div>
}
}
render(<MyComponent id="a" class="b" >
<div>abc</div>
<div>2</div>
</MyComponent>, document.body)
我们发现main.js在@babel/plugin-transform-react-jsx的翻译下,代码实际上会变成如下模样,@babel/plugin-transform-react-jsx会把首字母大写的自定义元素转成一个构造函数(类)如果是一个类,如在代码中创建一对元素,即我们需要实例化MyComponent并给实例化后的实例对象设置属性id和class,如果是普通元素如
我们直接使用dom自带的api 如果是元素节点则使用document.createElement(),如果是文本节点则使用document.createTextNode() 设置属性使用ele.setAttribute(name, value)下面的createElement执行顺序如下面数字序号
import { createElement, Component, render } from './toy-react'
class MyComponent extends Component {
render(){
return createElement(//第5步
"div",
null,
createElement("p", null, "MyComponent"),//第4步
this.children
)
}
}
render(
createElement(//第3步
MyComponent,
{
id: "a",
class: "b",
},
createElement("div", null, "abc"),//第1步
createElement("div", null, "2")//第2步
),
document.body
)
toy-react.js
实现toy-react 需要 元素节点ElementWrapper 文本节点TextWrapper Component createElement render 这些函数或类
// 元素节点
class ElementWrapper{
constructor(type) {
this.root = document.createElement(type)
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(component) {
this.root.appendChild(component.root)
}
}
// 文本节点
class TextWrapper{
constructor(content){
this.root = document.createTextNode(content)
}
}
// 组件
export class Component{
constructor(){
this.props = Object.create(null)//创建一个无_proto_的对象
this.children = []
this._root = null
}
setAttribute(name, value) {
this.props[name] = value
}
appendChild(component) {
this.children.push(component)
}
get root(){
if(!this._root) {
this._root = this.render().root
}
return this._root
}
}
// 文本节点
export function createElement(type, attributes, ...children) {
let e ;
if(typeof type === 'string') {
e = new ElementWrapper(type)//document.createElement(type)
} else {
e = new type
}
for(let p in attributes) {
e.setAttribute(p, attributes[p])
}
let insertChildren = function(children) {
for(let child of children){
if(typeof child === 'string'){
child = new TextWrapper(child)//document.createTextNode(child)
}
if(typeof child === 'object' && child instanceof Array){
insertChildren(child)
} else {
e.appendChild(child)
}
}
}
insertChildren(children)
return e
}
export function render(component, parentElement) {
parentElement.appendChild(component.root)
}