jsx到react的转化
混合js和html,将组件的结构,数据,样式聚合在一起定义组件。
这3张图片,可以看出来,首字母大写,会被定义为组件,小写会被标记为字符串,当成原本的标签,被编译。
createElement(type, config, children)
packages/react/src/ReactElement.js
创建元素的时候,会过滤ref和key,然后把别的config属性变成props添加上去,同时利用arguments 接收所有的children,利用type.defaultProps设置,用户没有主动为组件添加的属性的默认值。
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
react 元素的更改特性
react的元素在重新渲染的时候,内容变化的元素是会被替换的,内容固定的是不会被替换的。
const tick = () => {
const ele = (
<div>
<p>开始</p>
<span>{new Date().toLocaleTimeString()}</span>
<p>结束</p>
</div>
)
ReactDOM.render(ele, document.getElementById('time'))
}
componentDidMount() {
setInterval(tick, 1000);
}
可以看出date时间1秒重新渲染一次,所以,render的时候,span标签和span里面的内容会,重新变化,别的内容固定的元素,不会重新被渲染。
虚拟节点转化成真实节点
渲染原生组件
const element = (
<div className='title' style={{color: 'red'}}>
<span>hello</span>
world
</div>
)
console.log(JSON.stringify(element, null, 2));
ReactDOM.render(element, document.getElementById('root'))
jsx经过createElement转化之后,会得到虚拟dom
{
"type": "div",
"key": null,
"ref": null,
"props": {
"className": "title",
"style": {
"color": "red"
},
"children": [
{
"type": "span",
"key": null,
"ref": null,
"props": {
"children": "hello"
},
"_owner": null,
"_store": {}
},
"world"
]
},
"_owner": null,
"_store": {}
}
render
reder方法接收vDom(虚拟dom)和挂载容器,然后想办法得到真实的dom,vDom上主要包含props,type,children
- type 是元素的标签
- peops 元素的属性
- children 递归递归调用render
function render(vDom, container) {
const dom = createDom(vDom) // 创建真正的dom
container.appendChild(dom)
}
创建真实的dom
- 如果vDom只是字符串或者数字,创建创建文本节点
- vDom包含html标签,创建元素节点
- 为元素节点绑定,除children之外的props,特别是style
- 判断children,递归调用render,为当前的标签,插入子元素
function createDom(vDom) {
if (typeof vDom === 'string' || typeof vDom === 'number') {
return document.createTextNode(vDom)
}
const { type, props } = vDom;
// 原生组件可以直接渲染
let dom = document.createElement(type)
updateProps(dom, props)
if (typeof props.children === 'string' || typeof props.children === 'number') {
dom.textContent = props.children
} else if (typeof props.children === 'object' && props.children.type) {
// 单个children
render(props.children, dom)
} else if (Array.isArray(props.children)) {
// 多个children
reconcileChilren(props.children, dom)
} else {
dom.textContent = props.children ? props.children.toString() : ''
}
return dom
}
function reconcileChilren(childrenVdom, parentDom) {
for(let i=0; i<childrenVdom.length; i++) {
let childVdom = childrenVdom[i];
render(childVdom, parentDom)
}
}
function updateProps(dom, newProps) {
for(let key in newProps) {
if (key === 'children') continue;
if (key === 'style') {
let styleObj = newProps[key];
for(let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else {
dom[key] = newProps[key]
}
}
}
渲染函数组件
function Welcome(props) {
return (
<div className='title' style={{color: 'red'}}>
<span>hello</span>
{ props.children }
</div>
)
}
ReactDOM.render(
<Welcome>
<span>world</span>
</Welcome>,
document.getElementById('root'))
函数组件的渲染需要在,原生组件的渲染上,加上一些判断
函数组件的vDom中的,type是声明好的函数
{
"type": "Welcome",
"key": null,
"ref": null,
"props": {
"children": {
"key": null,
"ref": null,
"props": {
"children": "world"
},
"_owner": null,
"_store": {}
}
},
"_owner": null,
"_store": {}
}
所以在创建dom的时候增加判断createDom(vDom)
if (typeof type === 'function') {
dom = mountFunctionComponent(vDom)
} else {
dom = document.createElement(type)
}
执行函数组件,得到函数组件的vDom,然后执行createDom(vdom)
// 获取函数组件的vDom
function mountFunctionComponent(vDom) {
let {type: functionComponent, props} = vDom;
const renderVdom = functionComponent(props);
return createDom(renderVdom);
}
渲染类组件
类组件和函数组件的vDom中,type的类型都是function,在创建真实的dom的时候,需要在判断类型的时候,使用类组件里面的static isReactCompoment = true来识别当前是类组件,类组件的vDom就是render()的返回值。
- 类组件继承的Component
import { createDom } from './react-dom'
class Component {
static isReactComponent = true; // 标识当前是classComponent
constructor(props) {
this.props = props;
this.state = {};
}
setState(partialState) {
let state = this.state
this.state = {...state, ...partialState}
// 组件根据state 更新数据
let newVdom = this.render();
updateClassComponent(this,newVdom)
}
}
function updateClassComponent(classInstance, newVdom) {
let oldDom = classInstance.dom;
let newDom = createDom(newVdom);
oldDom.parentNode.replaceChild(newDom, oldDom);
classInstance.dom = newDom;
}
export default Component;
- createDom(vDom)识别类组件的虚拟dom,创建真实的dom
if (typeof type === 'function') {
// 类组件
if (type.isReactComponent) {
return mountClassComponent(vDom)
} else {
return mountFunctionComponent(vDom)
}
} else {
dom = document.createElement(type)
}
}
- 创建类组件的真实dom
function mountClassComponent(vDom) {
const {type: ClassComponent, props} = vDom;
const classInstance = new ClassComponent(props);
const renderVdom = classInstance.render(); // 虚拟dom
let dom = createDom(renderVdom);
classInstance.dom = dom; // 保留当前的dom,同于下次更新,替换dom,获取dom的父元素
return dom
}
- updateProps(dom, props)处理onClick事件
if (key.startsWith('on')) {
dom[key.toLocaleLowerCase()] = newProps[key];
}
- 根据数据,更新元素
根据新的新的虚拟dom,创建真实的dom之后,需要根据之前
mountClassComponent创建dom时,保存的dom,找到dom的父元素,替换成最新的真实的dom
这个过程发生在Component类中
setState更新了数据,同时调用updateClassComponent,更新dom
setState(partialState) {
let state = this.state
this.state = {...state, ...partialState}
// 组件根据state 更新数据
let newVdom = this.render();
updateClassComponent(this,newVdom)
}
function updateClassComponent(classInstance, newVdom) {
let oldDom = classInstance.dom;
let newDom = createDom(newVdom);
oldDom.parentNode.replaceChild(newDom, oldDom);
classInstance.dom = newDom;
}
updateClassComponent的组件目前是暴力替换元素的,消耗性能。之后的流程会优化
合成事件批量更新
setState事件在react的流程里面是会多个合并执行的,但是当放入异步事件里面就会安顺序执行。
handlerClick = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
setTimeout(() => {
console.log(this.state.count)
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}, 0)
}
// 0, 0, 1, 2, 3
把setTimeout换成promise得到结果也是一样的
手动实现批量更新/非批量更新
- 手动实现立即更新
import { createDom } from './react-dom'
export let updateQueue = {
isBatchUpdate: false, // 触发setState的时候,是否批量更新 false 不批量更新
updaters: new Set()
}
// 更新组件
class Updater {
constructor (classInstance) {
this.classInstance = classInstance; // 类组件实例
this.pendingStates = []; // 等待生效的状态,对象或者函数
this.callbacks = []; // 等待生效的状态,对象或者函数
}
addState(partialState, callback) {
this.pendingStates.push(partialState);
if (typeof callback === 'function') {
this.callbacks.push(callback);
}
if (updateQueue.isBatchUpdate) {
// 批量更新
updateQueue.updaters.add(this)
} else {
// 单独更新
this.updateClassComponent()
}
}
updateClassComponent() {
let {classInstance, pendingStates, callbacks} = this;
if (pendingStates.length > 0) {
classInstance.state = this.getState() // 获取新的状态
classInstance.forceUpdate()
callbacks.forEach(callback => callback())
}
}
getState() {
let {classInstance, pendingStates } = this;
let { state } = classInstance // 旧状态
// pendingStates是新状态
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state)
}
state = {...state, ...nextState}
})
pendingStates.length = 0;
return state;
}
}
class Component {
static isReactComponent = true; // 标识当前是classComponent
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let newVdom = this.render();
updateClassComponent(this, newVdom);
}
}
function updateClassComponent(classInstance, newVdom) {
let oldDom = classInstance.dom;
let newDom = createDom(newVdom);
oldDom.parentNode.replaceChild(newDom, oldDom);
classInstance.dom = newDom;
}
export default Component;
-
手动实现批量更新
componnet.js
import { createDom } from './react-dom'
export let updateQueue = {
isBatchingUpdate: false, // 触发setState的时候,是否批量更新 false 不批量更新
updaters: new Set(), // 存放所有的更新Updater实例
batchUpdate() { // 批量更新
for(let updater of this.updaters) {
updater.updateClassComponent()
}
this.isBatchingUpdate = false;
}
}
// 更新组件
class Updater {
constructor (classInstance) {
this.classInstance = classInstance; // 类组件实例
this.pendingStates = []; // 等待生效的状态,对象或者函数
this.callbacks = []; // 等待生效的状态,对象或者函数
}
addState(partialState, callback) {
this.pendingStates.push(partialState);
if (typeof callback === 'function') {
this.callbacks.push(callback);
}
if (updateQueue.isBatchingUpdate) {
// 批量更新
updateQueue.updaters.add(this)
} else {
// 单独更新
this.updateClassComponent()
}
}
updateClassComponent() {
let {classInstance, pendingStates, callbacks} = this;
if (pendingStates.length > 0) {
classInstance.state = this.getState() // 获取新的状态
classInstance.forceUpdate()
callbacks.forEach(callback => callback())
callbacks.length = 0; // 清空callback
}
}
getState() {
let {classInstance, pendingStates } = this;
let { state } = classInstance // 旧状态
// pendingStates是新状态
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state)
}
state = {...state, ...nextState}
})
pendingStates.length = 0;
return state;
}
}
class Component {
static isReactComponent = true; // 标识当前是classComponent
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let newVdom = this.render();
updateClassComponent(this, newVdom);
}
}
function updateClassComponent(classInstance, newVdom) {
let oldDom = classInstance.dom;
let newDom = createDom(newVdom);
oldDom.parentNode.replaceChild(newDom, oldDom);
classInstance.dom = newDom;
}
export default Component;
event.js
import { updateQueue } from './component';
export function addEvent(dom, eventType, listener) {
// listener 代表绑定的事件内容,即函数的主体
// eventType 代表绑定的事件 例如onClick
let store = dom.store || (dom.store = {});
store[eventType] = listener
if (!document[eventType]) {
document[eventType] = dispatchEvent; // 事件委托,把事件委托到document
}
}
let syntheticEvent = {};
function dispatchEvent(event) {
let { target, type } = event;
// target上会有store属性,应为原型链机制
let eventType = `on${type}`;
updateQueue.isBatchingUpdate = true; // 打开批量更新
createSyntheticEvent(event)
while(target) {
// while 实现冒泡机制
let { store } = target;
let listener = store && store[eventType];
listener && listener.call(target, syntheticEvent);
target = target.parentNode;
}
for(let key in syntheticEvent) {
syntheticEvent[key] = null;
}
updateQueue.batchUpdate()
}
function createSyntheticEvent(nativeEvent) {
for(let key in nativeEvent) {
syntheticEvent[key] = nativeEvent[key]
}
}
react-dom.js
function updateProps(dom, newProps) {
for(let key in newProps) {
if (key === 'children') continue;
if (key === 'style') {
let styleObj = newProps[key];
for(let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (key.startsWith('on')) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key])
} else {
dom[key] = newProps[key]
}
}
}
总结:
- 组件被创建的时候,会在
Componnet内部同步创建一个Updater的实例,这个Updater实例里面会保存,当前的组件实例,组件的state,和相关callback,同时这个new Updater实例也会被挂载在Component上。可以看出Component和Updater相互都拥有对方的实例信息。 - 更新发生在setState的时候,在setState被触发的时候,会调用
Updater里面的addState来收集当前的state和callback,都放入相应的数组中 - 批量更新实现,主要靠
updateQueue,updateQueue里搜集到了所有的Updater,每次触发setState的时候,都根据updateQueue.isBatchingUpdate来判断当前是批量更新还是单个更新,当updateQueue.isBatchingUpdate=true的时候,需要批量更新,当前的Updater就被存放在updateQueue.updaters里,当需要更新的时候,调用updateQueue.batchUpdate()来循环所有的Updater,更新组件的state执行setState方法中的callback函数和dom结构。 - 一个组件对应一个
Updater - 事件委托的机制,可以抹平,平台间的差异。并且在用操作的事件里,添加一个额外的操作,例如设置
updateQueue.isBatchingUpdate=true开启批量更新。因为批量更新是react内部控制的,根据js的事件循环机制,可以知道,setTimeout和promise内部写setState都是执行单个更新的流程。
react的dom-diff
dom-diff的比较是同级比较的,并且按照顺序由前向后
组件更新的时候,比较新旧的vdom,按照如下的规则
- oldVdom,newVdom都不存在,不做处理
- oldVdom存在,newVdom不存在,通过oldVdom查找当前真实dom节点,然后删除当前节点
- oldVdom不存在,newVdom存在,通过newVdom创建一个真实的dom,在判断后面有没有兄弟节点,有的话,插入兄弟节点的前面,没有的话,直接插入
- oldVdom,newVdom都存在,但是type类型不一致,根据newVdom创建真实dom,并且替换旧的dom
- oldVdom,newVdom都存在,但是type类型一致,直接从oldVdom上获取真实的dom, 然后判断一个type的类型
- string类型,更新props和children
- function类型,在判断是类,还是函数
- 类通过render获取vdom调用实例上的
updater.emitUpdate最终更新props和state - 函数执行函数获取vdom,然后继续比较孩子
- 类通过render获取vdom调用实例上的
ref
React.createRef
得到一个current属性为空的对象,在创建dom的时候,从虚拟节点上获取ref,并把真实的dom赋值给ref
function createRef() {
return { current: null }
}
if (ref) {
ref.current = dom;
}
React.useRef
值被保存在hookState中,所以useRef的值永远只想同一个地址
export function useRef(initVal) {
hookState[hookIndex] = hookState[hookIndex] || {current: initVal}
return hookState[hookIndex++]
}
React.forwardRef
让函数组件使用ref属性,内部返回一个继承了Component组件的类组件, Component的组件实例上挂有ref
function forwardRef(FunctionComponent) {
return class extends Component {
render() {
if (FunctionComponent.length < 2) {
console.error('===props, ref 参数缺一不可')
}
return FunctionComponent(this.props, this.ref);
}
}
}
React.useReducer
useReducer接受一个reducer函数,和初始值,内部返回一个dispatch函数体,这个函数内部,调用reducer函数,并且传入对应的参数
export function useReducer(reducer, initVal) {
hookState[hookIndex] = hookState[hookIndex] || (typeof initVal === 'function' ? initVal() : initVal);
let currentIndex = hookIndex;
function dispatch(action) {
// useState调用useReducer的时候,dispatch肯能接受的是一个函数
let lastState = hookState[currentIndex];
let nextState;
if(typeof action === 'function') {
nextState = action(lastState)
} else {
nextState = action
}
if (reducer) {
nextState = reducer(nextState, action)
}
hookState[currentIndex] = nextState;
scheduleUpdate(); // 状态改变之后,更新应用
}
return [hookState[hookIndex++], dispatch]
}
React.useState
return useReducer(null, initVal)
React.useEffect
- 接受函数,当前hookState里面没有对应的值,就执行函数(这个函数要放在宏任务中,保证在dom加载完成之后执行),把函数的返回值和依赖保存在hookstate中
- 当前hookState里面有对应的值,判断依赖有没有改变,改变了,执行返回值函数,重新保存新的callback产生的结果函数和依赖,以来没改变只需更新hookIndex
export function useEffect(callback, deps) {
if (hookState[hookIndex]) {
let [destoryFactory, lastDeps] = hookState[hookIndex]
const same = deps && deps.every((item, index) => (item === lastDeps[index]))
if (same) {
hookIndex++
} else {
destoryFactory && destoryFactory(); // effect 的 return 函数
setTimeout(() => {
let destoryFactory = callback()
hookState[hookIndex++] = [destoryFactory, deps]
})
}
} else {
// setTimeout 是为了模拟宏任务,让effect在dom加载完毕之后执行
setTimeout(() => {
let destoryFactory = callback()
hookState[hookIndex++] = [destoryFactory, deps]
})
}
}
React.useMemo/React.memo
React.memo
减少函数组件的更新次数,当子组件用memo包裹的时候,当自己的数据没有变化的时候,就不会更新,要配和useMemo使用
function memo(FunctionComponent) {
return class extends PureComponent {
render() {
return FunctionComponent(this.props)
}
}
}
React.useMemo
可以缓存变量,hookState里面有对应的值的时候,依赖项不变的话,数据不变,依赖改变,传入的函数。重新调用
export function useMemo(factory, deps) {
if (hookState[hookIndex]) {
let [lastMemo, lastDeps] = hookState[hookIndex]
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastMemo;
} else {
let newMemo = factory();
hookState[hookIndex++] = [newMemo, deps];
return newMemo
}
} else {
let newMemo = factory();
hookState[hookIndex++] = [newMemo, deps];
return newMemo
}
}
React.useCallback
可以缓存方法
export function useCallback(callback, deps) {
if (hookState[hookIndex]) {
let [lastCallback, lastDeps] = hookState[hookIndex]
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastCallback;
} else {
hookState[hookIndex++] = [callback, deps];
return callback
}
} else {
hookState[hookIndex++] = [callback, deps];
return callback
}
}
useMemo保存函数体执行之后的值,而useCallback保存函数体
React.createContext
获取用户传入的props.value值,并且绑定在context上。
function createContext(initVal = {}) {
let context = { Provider, Consumer }
function Provider(props) {
context._currentValue = context._currentValue || initVal
Object.assign(context._currentValue, props.value)
return props.children;
}
function Consumer(props) {
return props.children(context._currentValue)
}
return context;
}
React.useContext
只是返回React.createContext()的_currentValue
function useContext(context) {
return context._currentValue;
}