react的每次更新都是从根组件开始
class ClassCounter extends React.PureComponent{
render(){
return <div>ClassCounter:{this.props.count}</div>
}
}
function FunctionCounter(props){
return <div>FunctionCounter:{props.count}</div>
}
const MemoFunctionCounter = React.memo(FunctionCounter);
class App extends React.Component{
state = {number:0}
amountRef = React.createRef()
handleClick = ()=>{
let nextNumber = this.state.number + (parseInt(this.amountRef.current.value));
this.setState({
number:nextNumber
});
}
render(){
return (
<div>
<ClassCounter count={this.state.number}/>
<MemoFunctionCounter count={this.state.number}/>
<input ref={this.amountRef}/>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
上面的案例中,如果input里面输入了0,那么点击➕按钮,重新渲染组件,前后两次属性完全一样,所以没必要再渲染,但如果不做任何处理,react还是会再渲染一遍
此时,就可以通过让组件继承React.PureComponent(如上述代码所示)来解决
而对于函数组件来说,可以用React.memo包一下(如上述代码所示)来解决
class PureComponent extends Component{
shouldComponentUpdate(nextProps,nextState){
//只要属性和状态对象,有任意一个属性变了,就会进行更新。如果全相等,才不更新
return !shallowEquals(this.props,nextProps) || !shallowEquals(this.state,nextState)
}
}
function memo(type,compare=shallowEquals){
return {
$$typeof:REACT_MEMO,
type,//函数组件
compare
}
}
const React = {
createElement,
Component,
createRef,
forwardRef,
Fragment:REACT_FRAGMENT,
createContext,
PureComponent,
memo
}
初次创建DOM时,增加对REACT_MEMO类型的支持:
export function createDOM(vdom) {
if (!vdom) return null;
let { type, props, ref } = vdom;
let dom;//真实DOM
if(type && type.$$typeof === REACT_MEMO){
return mountMemo(vdom);
function mountMemo(vdom){
//type = {$$typeof:REACT_MEMO,type,//函数组件compare}
let { type, props } = vdom; // type.type 函数组件
let renderVdom = type.type(props);
vdom.prevProps= props;//在vdom记录上一次的属性对象
vdom.oldRenderVdom = renderVdom;//findDOM的时候用的
return createDOM(renderVdom);
}
更新DOM时,增加对REACT_MEMO类型的支持:
function updateElement(oldVdom, newVdom) {
//Provider更新
if(oldVdom.type.$$typeof === REACT_MEMO){
updateMemo(oldVdom, newVdom);
function updateMemo(oldVdom, newVdom){
let {type,prevProps} = oldVdom;
//比较结果是相等,就不需要重新渲染 render
let renderVdom=oldVdom.oldRenderVdom;
if(!type.compare(prevProps,newVdom.props)){
let currentDOM = findDOM(oldVdom);
let parentDOM = currentDOM.parentNode;
let {type,props} = newVdom;
renderVdom = type.type(props);
compareTwoVdom(parentDOM,oldVdom.oldRenderVdom,renderVdom);
}
newVdom.prevProps = newVdom.props;
newVdom.oldRenderVdom = renderVdom;
}
对于弹框这种交互,不应该把DOM渲染到子组件中,而应该插入到body中,react通过createPortal实现这一点
class Dialog extends React.Component{
constructor(props){
super(props);
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
componentWillUnmount(){
document.body.removeChild(this.node);
}
render(){
//把一个JSX,也就是一个虚拟DoM元素渲染到地应的DOM节点中
return ReactDOM.createPortal(
<div className="dialog">
{this.props.children}
</div>,
this.node
);
}
}
class App extends React.Component{
render(){
return (
<div>sss
<Dialog>模态窗口</Dialog>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
实现:
const ReactDOM = {
render,
createPortal:render