2020 动手写个 react (2)
coding
我们写一段 jsx ,有关 jsx 语法这里暂不做过介绍,留下伏笔
const ele = (
const ele = (
hello zidea
)
)
然后将上面 jsx 通过 babel 转换为 js 如下
"use strict";
const ele = /*#__PURE__*/React.createElement("div", {
className: "box",
size: "25"
}, "hello ", /*#__PURE__*/React.createElement("span", null, "zidea"));
有了上面生成代码,我们简单地分析一下
React.createElement(tag,attrs,children)
tag 是要创建标签名称
attrs 是以对象形式组织属性对象,对象中以键值对形式保存属性名和值
children 是若干个子元素
实现 createElement 方法
创建 React 对象
const React = {
createElement
}
实现 creatElement 方法
// createElement
function createElement(tag,attrs,...children){
return {
tag,
attrs,
children
}
}
export default React;
引入 React 对象,使用 React 的 createElement 方法
import React from './react'
const ele = (
hello react
)
console.log(ele)
图
实现渲染
我们都知道在 React 中调用 ReactDom 的 render 方法然后传入虚拟节点和要将虚拟节点添加容器 dom 元素就可以实现将虚拟节点渲染到页面。
ReactDOM.render(ele,document.querySelector("#root"))
import React from './react'
const ele = (
hello react
)
// console.log(ele)
ReactDOM.render(ele,document.querySelector("#root"))
接下依旧是创建文件夹 react-dom 然后在该文件夹下创建 index.js 文件实现 ReactDOM 的 render 方法
const ReactDOM = {
render
}
function render(vnode,container){
//TODO
}
export default ReactDOM;
有关常见一个 js 模块并且将 ReactDOM 对象暴露出来我就不做过多解释了。接下来我们就专注 render 方法的实现。
文本节点实现
function render(vnode,container){
//TODO
if(vnode === undefined ) return;
// vnode is equal string
if(typeof vnode === 'string'){
//create textNode
const textNode = document.createTextNode(vnode)
return container.appendChild(textNode)
}
}
参数和 react 没有什么区别
vnode 虚拟节点
container 容器
首选判断 vnode 是否为空,为空则直接返回
然后判断 vnode 是否为字符串,如果为字符串这添加文本节点
看效果
ReactDOM.render('react',document.querySelector("#root"))
虚拟节点
ReactDOM.render(ele,document.querySelector("#root"));
console.log(vnode)
function render(vnode,container){
...
// deconstruct vnode
const {tag} = vnode;
//create dom object
const dom = document.createElement(tag)
container.appendChild(dom)
}
解构 vnode 对象
根据 tag 创建 dom 对象
将 dom 对象添加到容器中
获取属性
function render(vnode,container){
...
// deconstruct vnode
const {tag,attrs} = vnode;
//create dom object
const dom = document.createElement(tag)
if(attrs){
// property key: className box
Object.keys(attrs).forEach(key=>{
const val = attrs[key]
})
}
...
}
从虚拟节点(vnode)中解构出 attrs
然后遍历 attrs 获取属性值
设置属性
function setAttribute(dom,key,value){
}
实现 setAttribute 方法
function setAttribute(dom,key,value){
// convert className to class
// 1. event 2.class 3.style etc
// class case
if(key === 'className'){
key = 'class'
}
// event case
if(/on\w+/.test(key)){
//to lower case
key = key.toLowerCase();
dom[key] = value || ''
}else if(key === 'style'){
if(!value || typeof value === 'string'){
dom.style.cssText = value || '';
}else if(value && typeof value === 'object'){
//{width:16}
for(let k in value){
if(typeof value[k] === 'number'){
dom.style[k] = value[k] + 'px'
}else{
dom.style[k] = value[k]
}
}
}
}else{
if(key in dom){
dom[key] = value || ''
}
if(value){
dom.setAttribute(key,value)
}else{
dom.removeAttribute(key)
}
}
}
在 dom 上有许多属性,可以通过属性添加样式,添加事件,保存数据等。我们这里通过判断 attrs 中属性键和值类型来一一进行判断
class 属性
如果键为 className 时,我们将键修改 class 虽然添加值
if(key === 'className'){
key = 'class'
}
事件属性
if(/on\w+/.test(key)){
//to lower case
key = key.toLowerCase();
dom[key] = value || ''
}
样式属性
else if(key === 'style'){
if(!value || typeof value === 'string'){
dom.style.cssText = value || '';
}else if(value && typeof value === 'object'){
//{width:16}
for(let k in value){
if(typeof value[k] === 'number'){
dom.style[k] = value[k] + 'px'
}else{
dom.style[k] = value[k]
}
}
}
}
样式情况相对于其他属性要复杂一些,分两种情况处理可能是字符串或者一个对象
字符串情况直接赋值即可
对象情况
在开始处理对象情况,我们先看一看下面 jsx 对象编译为 javascript 对象后默认
const ele = (
hello zidea
)
...
style: {
width: 16
}
...
这里 style 对象中嵌套一个对象,我们通过解析对象来获取每一个样式的名称和样式值。样式值可能是数值也可能是字符串。
for(let k in value){
if(typeof value[k] === 'number'){
dom.style[k] = value[k] + 'px'
}else{
dom.style[k] = value[k]
}
}
if(attrs){
// property key: className box
Object.keys(attrs).forEach(key=>{
const val = attrs[key]
setAttribute(dom,key,val)
})
}
container.appendChild(dom)
渲染子节点
通过递归形式来实现渲染子节点
function render(vnode,container){
...
vnode.children.forEach(child=>render(child,dom))
container.appendChild(dom)
}
发表评论 (审核通过后显示评论):