前言
原版示例对照
- 做生命周期思路其实不难,在更新的地方已经可以拿到类的实例,然后进行调用判断即可。
- 先做个示例对照:
class Child extends React.Component{
componentWillMount(){
console.log('child willmount')
}
componentDidMount(){
console.log('child didmount')
}
shouldComponentUpdate(nextProps,nextState){
console.log('child shouldmount')
return nextProps.number>2
}
componentWillUpdate(){
console.log('child willupdate')
}
componentDidUpdate(){
console.log('child didupdate')
}
componentWillUnmount(){
console.log('child unmount')
}
render(){
console.log('childrender')
return <div>{this.props.number}</div>
}
}
class Counter extends React.Component{
static defaultProps = {name:'yehuozhili'}
constructor(props){
super(props)
this.state={number:0}
console.log('xxx')
}
componentWillMount(){
console.log('willmount')
}
componentDidMount(){
console.log('didmount')
}
shouldComponentUpdate(nextProps,nextState){
console.log('shouldmount')
return nextState.number>1
}
componentWillUpdate(){
console.log('willupdate')
}
componentDidUpdate(){
console.log('didupdate')
}
handleClick=()=>{
this.setState((state)=>({number:state.number+1}))
}
render(){
console.log('render')
return (
<div>
<p>{this.state.number}</p>
{
this.state.number>3?null:<Child number={this.state.number}></Child>
}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
ReactDOM.render(
<Counter></Counter>,
document.getElementById('root')
);
xxx
index.js:37 willmount
index.js:56 render
index.js:6 child willmount
index.js:25 childrender
index.js:9 child didmount
index.js:40 didmount
2index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:12 child shouldmount
index.js:50 didupdate
index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:12 child shouldmount
index.js:16 child willupdate
index.js:25 childrender
index.js:19 child didupdate
index.js:50 didupdate
index.js:43 shouldmount
index.js:47 willupdate
index.js:56 render
index.js:22 child unmount
index.js:50 didupdate
实现willmount didmount
- willmount和willupdate会和getSnapshotBeforeUpdate冲突,所以后面在搞这个。
- 这2插入地方在创建类组件真实dom处:
function createClassComponetDOM(element){
let {type,props}=element
let componentInstance =new type(props)
if(componentInstance.componentWillMount){
componentInstance.componentWillMount()
}
let renderElement = componentInstance.render()
componentInstance.renderElement=renderElement
element.componentInstance=componentInstance
let newDom =createDOM(renderElement)
if(componentInstance.componentDidMount){
componentInstance.componentDidMount()
}
return newDom
}
实现willupdate didupdate
- 这个上次实现了,位置在Component的forceupdate里面:
Component.prototype.forceUpdate = function() {
let {renderElement}=this
if(this.componentWillUpdate){
this.componentWillUpdate()
}
let newRenderElement =this.render()
let currentElement =compareTwoElement(renderElement,newRenderElement)
this.renderElement = currentElement
if(this.componentDidUpdate){
this.componentDidUpdate()
}
};
实现shouldComponentUpdate
function shouldUpdate(componentInstance,nextProps,nextState){
let scu=componentInstance.shouldComponentUpdate&&!componentInstance.shouldComponentUpdate(nextProps,nextState)
componentInstance.props =nextProps
componentInstance.state = nextState
if(scu){
return false
}
componentInstance.forceUpdate()
}
实现willUnmount
- 挂载点在domdiff函数上,如果新Map上没有这个索引的元素,就卸载掉。
function diff(parentNode,oldChildrenElements,newChildrenElements,diffQueue){
let oldChildrenElementsMap=getChildrenElementsMap(oldChildrenElements)
let newChildrenElementsMap=getNewChildrenElementsMap(newChildrenElements,oldChildrenElementsMap)
let lastIndex=0
for(let i=0;i<newChildrenElements.length;i++){
let newChildElement=newChildrenElements[i]
if(newChildElement){
let newKey=newChildElement.key||i.toString()
let oldChildElement=oldChildrenElementsMap[newKey]
if(newChildElement===oldChildElement){
if(oldChildElement._mountIndex<lastIndex){
diffQueue.push({
parentNode,
type:MOVE,
fromIndex:oldChildElement._mountIndex,
toIndex:i
})
}
lastIndex=Math.max(oldChildElement._mountIndex,lastIndex)
}else{
diffQueue.push({
parentNode,
type:INSERT,
toIndex:i,
dom:createDOM(newChildElement)
})
}
newChildElement._mountIndex=i
}else{
let newKey=i.toString()
if(oldChildrenElementsMap[newKey].componentInstance&&oldChildrenElementsMap[newKey].componentInstance.componentWillUnmount){
oldChildrenElementsMap[newKey].componentInstance.componentWillUnmount()
}
}
}
for(let oldkey in oldChildrenElementsMap){
if(!newChildrenElementsMap.hasOwnProperty(oldkey)){
let oldChildElement=oldChildrenElementsMap[oldkey]
diffQueue.push({
parentNode,
type:REMOVE,
fromIndex:oldChildElement._mountIndex,
})
}else{
let oldChildElement = oldChildrenElementsMap[oldkey];
let newChildElement = newChildrenElementsMap[oldkey];
if (oldChildElement !== newChildElement) {
diffQueue.push({
parentNode,
type: REMOVE,
fromIndex: oldChildElement._mountIndex
});
}
}
}
}
实现componentWillReceiveProps
- 这个就是可以获得将要收到的属性。
- 挂载点在更新类组件函数上:
function updateClassComponent(oldelement,newelement){
let componentInstance = oldelement.componentInstance
let updater = componentInstance.updater
let nextProps = newelement.props
if(componentInstance.componentWillReceiveProps){
componentInstance.componentWillReceiveProps(nextProps)
}
updater.emitUpdate(nextProps)
}
实现getDerivedStateFromProps
- 这个生命周期是用来替代上面那个的,是类的静态属性,可以对传入的props做自己的state的映射,如果自己组件没有state,会报警告。
- 看例子:
class Child extends React.Component{
constructor(props){
super(props)
this.state={number:0}
}
static getDerivedStateFromProps(nextProps,prevState){
const {number}=nextProps
if(number%2===0){
return {number:number*2}
}else{
return {number:number*3}
}
}
render(){
console.log('childrender')
return <div>{this.state.number}</div>
}
}
class Counter extends React.Component{
static defaultProps = {name:'yehuozhili'}
constructor(props){
super(props)
this.state={number:0}
}
handleClick=()=>{
this.setState((state)=>({number:state.number+1}))
}
render(){
console.log('render')
return (
<div>
<p>{this.state.number}</p>
{
this.state.number>3?null:<Child number={this.state.number}></Child>
}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
- 父组件每次加一,传入组件判断父组件传的props是奇数还是偶数,奇数3,偶数2。
- 挂载点和上面一样:
function updateClassComponent(oldelement,newelement){
let componentInstance = oldelement.componentInstance
let updater = componentInstance.updater
let nextProps = newelement.props
if(componentInstance.componentWillReceiveProps){
componentInstance.componentWillReceiveProps(nextProps)
}
if(newelement.type.getDerivedStateFromProps){
let newState =newelement.type.getDerivedStateFromProps(nextProps,componentInstance.state)
if(newState){
componentInstance.state={...componentInstance,...newState}
}
}
updater.emitUpdate(nextProps)
}
- 同时,第一次挂载需要调用一次,挂载点就在创建真实dom那:
function createClassComponetDOM(element){
let {type,props}=element
let componentInstance =new type(props)
if(componentInstance.componentWillMount){
componentInstance.componentWillMount()
}
if(type.getDerivedStateFromProps){
let newState =type.getDerivedStateFromProps(props,componentInstance.state)
if(newState){
componentInstance.state={...componentInstance,...newState}
}
}
let renderElement = componentInstance.render()
componentInstance.renderElement=renderElement
element.componentInstance=componentInstance
let newDom =createDOM(renderElement)
if(componentInstance.componentDidMount){
componentInstance.componentDidMount()
}
return newDom
}
实现getSnapshotBeforeUpdate
- 这个方法配合didupdate使用的,返回值会传给didupdate的第三个参数,所以修改下:
Component.prototype.forceUpdate = function() {
let {renderElement,props,state}=this
if(this.componentWillUpdate){
this.componentWillUpdate()
}
let {getSnapshotBeforeUpdate}=this
let extra=getSnapshotBeforeUpdate&&getSnapshotBeforeUpdate(props,state)
let newRenderElement =this.render()
let currentElement =compareTwoElement(renderElement,newRenderElement)
this.renderElement = currentElement
if(this.componentDidUpdate){
this.componentDidUpdate(props,state,extra)
}
};