JSX实际是一个javascript 语法扩展,在react 代码执行之前,babel先将JSX编译为React API,即Virtual Dom,再将Virtual Dom 转换为真实Dom对象,最后将真实Dom对象显示到页面上。所以他实际是javascript,目的是为了开发者编写页面更加轻松。
<div className = 'box'>
<h1>你好</h1>
<p>世界</p>
</div>
<--------------------------------------->
React.createElement(
"div",
{
className:"box"
},
React.createElement(
"h1",
null,
"你好"
),
React.createElement(
"p",
null,
"世界"
),
)
在React中,每个Dom对象都有一个对应的Virtual DOM 对象,他是DOM对象的javaScript对象表现形式,使用javascript 对象来描述DOM对象的信息。
<div className = 'box'>
<h1>你好</h1>
<p>世界</p>
</div>
<---------------------------------------->
{
type:'div',
props:{className:"box"},
children :[
{
type:'h1',
props:null,
children:[
{
type:'text',
props:{
textContent:'你好'
}
}
]
},
{
type:'p',
props:null,
children:[
{
type:'text',
props:{
textContent:'世界'
}
}
]
},
]
}
那么为什么使用Virtual Dom 能够提高更新的效率呢?
因为第一次生成的Dom对象就会有一个对应的Virtual Dom,在代码更新后,实际Dom更新之前,又会生成一个新的Virtual Dom,此时,将最新的Virtual Dom和旧的Virtual Dom作出对比,只把有变化的部分再更新到实际的Dom对象。
现在就模拟react创建VirtualDom 对象的过程。
首先,babel 会默认将react代码转换成React.createElement ,我们需要更改babel的配置,让jsx代码转换自己写的TinyReact.createElement 。在根目录中创建.babelrc 文件,添加如下配置
{
"presets":[
"@babel/preset-env",
[
"@babel/preset-react",
{
"pragma":"TinyReact.createElement"
}
]
]
}
建一个createElement 文件
export default function createElement (type,props,...children){
// 判断是否为普通文本,剔除无效的dom结构(if语句等)
const childElement = [].concat(...children).reduce((result,child )=>{
if(child !== false && child !== true && child !== null){
if(child instanceof Object) {
result.push(child)
}else {
result.push(createElement("text",{textContent:child}))
}
}
return result
},[])
return {
type,
props:Object.assign({children:childElement},props),
childElement
}
}
//区分是否是普通VirturlDom ,还是组件. form ---mountElement.js
import mountNativeElement from './mountNativeElement.js'
import isFunction from './isFunction.js'
import mountComponent from './mountComponent.js'
export default function mountElement (virtualDom,container){
// Component or NativeElement(普通jsx 元素)
if(isFunction(virtualDom)){
//渲染组件
mountComponent(virtualDom,container)
}else{
//渲染virtual Dom 节点
mountNativeElement (virtualDom,container)
}
}
//附注:
export default function isFunction(virtualDom){
return virtualDom && typeof virtualDom.type === "function"
}
// 如果是virtual Dom 节点 组件,则渲染 from---createDomElement.js
import mountElement from './mountElement.js'
import updateNodeElement from './updateNodeElement.js'
export default function createDomElement (virtualDom){
let newElement = null
if(virtualDom.type === "text"){
// 文本节点
newElement = document.createTextNode (virtualDom.props.textContent)
}else {
// 元素节点
newElement = document.createElement (virtualDom.type)
// 添加属性
updateNodeElement (newElement,virtualDom)
}
// 递归创建子节点
virtualDom.childElement.forEach(child =>{
mountElement(child,newElement)
})
return newElement
}
// form ---- mountNativeElement.js
import createDomElement from './createDomElement.js'
export default function mountNativeElement (virtualDom , container){
let newElement = createDomElement(virtualDom)
// 将转换之后的Dom对象放置在页面中
container.appendChild(newElement)
}
export default function updateNodeElement (newElement,virtualDom){
const newProps = virtualDom.props
Object.keys(newProps).forEach(propName =>{
// 获取属性值
const newPropsValue = newProps[propName]
// 判断是否是事件属性
if(propName.slice(0,2) === "on"){
// 事件名称
const eventName = propsName.toLowerCase().slice(2)
newElement.addEventListener(eventName,newPropsValue)
}else if (propName === "value" || propName === "checked"){
newElement[propName] = newPropsValue
}else if(propName !== "children"){
if(propName === "className"){
newElement.setAttribute('class',newPropsValue)
}else {
newElement.setAttribute(propName,newPropsValue)
}
}
})
}
import isFunction from './isFunction.js'
export default function isFunctionComponent (virtualDom){
const type = virtualDom.type
return (
type && isFunction(virtualDom) && !(type.prototype && type.prototype.render )
)
import isFunctionComponent from './isFunctionComponent.js'
import mountNativeElement from './mountNativeElement.js'
import isFunction from './isFunction'
export default function mountComponent (virtualDom,container){
let nextVirtualDom = null
// 判断组件组件是函数组件还是组件
if(isFunctionComponent(virtualDom)){
// 函数组件
nextVirtualDom = buildFunctionComponent(virtualDom)
}
// 判断是否返回的就是普通virtral Dom,如果不是,则继续调用
if(isFunction (nextVirtualDom)) {
mountComponent(nextVirtualDom,container)
} else{
mountNativeElement(nextVirtualDom,container)
}
}
function buildFunctionComponent (virtualDom) {
// 调用virtualDom.type 的方法
return virtualDom.type(virtualDom.props)
}
function buildFunctionComponent (virtualDom) {
// 调用virtualDom.type 的方法
return virtualDom.type(virtualDom.props)
}
function buildClassComponent (virtualDom){
const component = new virtualDom.type(virtualDom.props || {})
const nextVirtualDom = component.render()
return nextVirtualDom
}
// from ---- index.js
class Alert extends TinyReact.Componet {
constructor(props){
super(props)
}
render(){
return <div>{this.props.name}</div>
}
}
TinyReact.render(<Alert name = "张三" />,root)
// from -----component.js
(1)元素节点相同
// 无元素节点的时候
if(!oldDom){
console.log(33); //不打印
mountElement (virtualDom, container)
//当Dom元素都相同的时候
}else if(oldVirtualDom && virtualDom.type === oldVirtualDom.type){
// 当元素是text 的时候
if(virtualDom.type === "text"){
//更新内容
updateTextElement(virtualDom, container, oldDom)
} else {
// 更新元素属性
updateNodeElement(oldDom, virtualDom, oldVirtualDom)
}
// 循环内部的元素进行更新
virtualDom.children.forEach((child,i) => {
diff(child,oldDom,oldDom.children[i])
})
}
//更新文字方法
export default function updateTextElement(virtualDom, container, oldDom){
if(virtualDom.props.textContent !== oldDom.props.textContent){
oldDom.textContent = virtualDom.props.textContent
oldDom._virtualDom = virtualDom
}
}
//更新 元素方法
export default function updateNodeElement (newElement,virtualDom,oldVirtualDom = {}){
const newProps = virtualDom.props || {}
const oldProps = oldVirtualDom.props || {}
// 替换属性的值
Object.keys(newProps).forEach(propName =>{
// 获取属性值
const newPropsValue = newProps[propName]
const oldPropsValue = oldProps[propName]
if(newPropsValue !== oldPropsValue){
// 判断是否是事件属性
if(propName.slice(0,2) === "on"){
// 事件名称
const eventName = propsName.toLowerCase().slice(2)
newElement.addEventListener(eventName,newPropsValue)
// 移除原来的事件处理函数
if(oldPropsValue){
newElement.removeEventListener(eventName,oldPropsValue)
}
}else if (propName === "value" || propName === "checked"){
newElement[propName] = newPropsValue
}else if(propName !== "children"){
if(propName === "className"){
newElement.setAttribute('class',newPropsValue)
}else {
newElement.setAttribute(propName,newPropsValue)
}
}
}
})
// 如果属性有被删除
Object.keys(oldProps).forEach(propName =>{
const newPropsValue = newProps[propName]
const oldPropsValue = oldProps[propName]
// 如果新的属性上没有值,则表示被删除
if(!newPropsValue){
if(propName.slice(0,2) === "on"){
const EventName = propName.toLocaleLowerCase().slice(2)
newElement.removeEventListener(EventName,oldPropsValue)
} else if (propName !== "children"){
newElement.removeAttribute(propName)
}
}
})
}
(2)元素节点不相同(同级节点比较,深度优先)
else if ( virtualDom.type !== oldVirtualDom.type && typeof virtualDom !== "function" ){
const newDom = createDomElement(virtualDom)
oldDom.parentNode.replaceChild(newDom,oldDom)
}
// 如果属性有被删除
Object.keys(oldProps).forEach(propName =>{
const newPropsValue = newProps[propName]
const oldPropsValue = oldProps[propName]
// 如果新的属性上没有值,则表示被删除
if(!newPropsValue){
if(propName.slice(0,2) === "on"){
const EventName = propName.toLocaleLowerCase().slice(2)
newElement.removeEventListener(EventName,oldPropsValue)
} else if (propName !== "children"){
newElement.removeAttribute(propName)
}
}
})
十二、setState 实现类组件更新,并添加生命周期函数
关键代码
class Alert extends TinyReact.Componet {
constructor(props){
super(props)
this.state = {
title:'title'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (){
this.setState({title:"change title"})
}
render(){
return <div>
{this.props.name}
{this.state.title}
<button onClick = {this.handleClick()}>点击</button>
</div>
}
}
TinyReact.render(Alert ,root)
<---------------------------------------------------------------->
//from ---- component.js
export default class Componet {
constructor(props) {
this.props = props
}
setState(state){
this.setState = Object.assign({}, this.state, state)
let virtualDom = this.render()
let oldDom = this.getDom()
let container = oldDom.parentNode
diff(virtualDom,container,oldDom)
}
setDom(dom) {
this._dom = dom
}
getDom(){
return this._dom
}
updateProps(props){
this.props = props
}
// 生命周期函数
componentWillMount ()
componentDidMount()
componentWillReceiveProps()
shouldComponentUpdate(nextProps,nextState){
return nextProps != this.props || nextState != this.state
}
componentWillUpdate(nextProps,nextState){}
componentDidUpdate(nextProps,nextState){}
componentWillUmount(){}
}
<------------------------------------------------------ >
//form ----- diff.js
// setState 更新类组件关键代码
}else if ( typeof virtualDom === "function"){
// 是组件
diffComponent(virtualDom, oldComponent, oldDom, container)
}
<------------------------------------------------------>
//from ------diffComponent.js
import mountElement from './mountElement'
import updateComponent from './updateComponent.js'
export default function diffComponent (
virtualDom,
oldComponent,
oldDom,
container
){
if(isSameComponent(virtualDom, oldComponent)){
// 同一个组件,做组件更新工作,替换props
updateComponent(virtualDom, oldComponent, oldDom, container)
}else{
// 不是同一个组件,直接替换组件
mountElement(virtualDom, container, oldComponent, oldDom)
}
}
// 判断是否是同一个组件
function isSameComponent(virtualDom,oldComponent){
return oldComponent && virtualDom.type() === oldComponent.constructor
}
<--------------------------------->
//from -------updateComponent.js。如果是同一个组件
import diff from "./diff";
export default function updateComponent (
virtualDom,
oldComponent,
oldDom,
container
){
oldComponent.componentWillReceiveProps(virtualDom.props)
if(oldComponent.shouldComponentUpdate(virtualDom.props)){
let prevProps = oldComponent.props
oldComponent.componentWillUpdate(virtualDom.props)
// 组件更新
oldComponent.updateProps(virtualDom.props)
// 获取组件返回最新的 virtualDom
let nextVirtualDom = oldComponent.render()
// 更新 component 组件实例对象
nextVirtualDom.component = oldComponent
// 比对
diff(nextVirtualDom, container, oldDom)
oldComponent.componentDidUpdate(prevProps)
}
}
<------------------------------------->
//from mountElement.js 不是同一个组件,替换删除
import mountNativeElement from './mountNativeElement.js'
import isFunction from './isFunction.js'
import mountComponent from './mountComponent.js'
//添加oldDom以替换
export default function mountElement(virtualDom, container, oldDom) {
// Component or NativeElement(普通jsx 元素)
if(isFunction(virtualDom)){
mountComponent(virtualDom, container, oldDom)
}else{
mountNativeElement (virtualDom, container, oldDom)
}
}
//from mountNativeElement.js
import createDomElement from './createDomElement.js'
import unmountNode from './unmountNode'
export default function mountNativeElement (virtualDom , container, oldDom){
let newElement = createDomElement(virtualDom)
// 判断旧的Dom是否存在,存在删除
if(oldDom){
unmountNode(oldDom)
}
// 将转换之后的Dom对象放置在页面中
container.appendChild(newElement)
let component = virtualDom.component
if(component){
component.setDom(newElement)
}
}
十三、ref 获取dom 和组件
//from index.js
handleClick (){
// this.setState({title:"change title"})
console.log(this.input.value);
// 获取sh
console.log(this.alert);
}
render(){
return <div>
{this.props.name}
{this.state.title}
<input type="text" ref={input => {this.input = input}}></input>
<Alert ref={alert => this.alert = alert }></Alert>
<button onClick = {this.handleClick()} >点击</button>
</div>
}
<------------------------------------------>
//获取 最新Dom 元素
//from ----createDomElement.js
if(virtualDom.props && virtualDom.props.ref){
virtualDom.props.ref(newElement)
}
//获取组件实例对象
//from -----mountComponent
let nextVirtualDom = null
let component = null
// 判断组件组件是函数组件还是组件
if(isFunctionComponent(virtualDom)){
// 函数组件
nextVirtualDom = buildFunctionComponent(virtualDom)
}else{
// 类组件‘
nextVirtualDom = buildClassComponent(virtualDom)
component = nextVirtualDom.component
}
if(component){
if(component.props && component.props.ref){
component.props.ref(component)
}
}