最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何实现一个mini版的React(二)

    正文概述 掘金(ほうこう)   2021-03-14   430

    如何实现一个mini版的React(二)

    上一篇文章讲到, JSX是什么,虚拟DOM是什么,JSX如何转成虚拟dom 虚拟DOM如何转成真是dom, 接着上一篇开始总结下:

    • React组件的实现
    • React组件里面的生命周期实现

    1、React 组件的实现

    回顾一下上一篇文章中,我们传入ReactDOM.render的第一个参数是JSX的虚拟dom,我们现在将他改成class组件

    // index.js
    import React from 'react';
    import ReactDOM from 'react-dom'
    //如果是jsx对象
    const JSX = (
        <div className="active" >
            hello,<span>mini react</span>
        </div>
    )
    //如果是class
    class Comp extends React.Component {
        render() {
            return (
                <div className="active" >
                    hello,<span>mini react</span>
                </div>
            )
        }
    }
    ReactDOM.render(<Comp />, document.querySelector('#app'));
    

    可以看到 Comp 需要继承React.Component

    2、React.Component

    // react/Component.js
    class Component {
        constructor( props = {} ) {
            this.state = {};
            this.props = props;
        }
    }
    export default Component;
    
    import Component from './Component'
    // ...
    const React = {
        createElement,
        Component
    }
    export default React
    
    • React.Component先这么写上

    3、分析下 ReactDOM.render 第一个参数变成<Comp />后的结果

    • babel transform-react-jsx转译class的时候输出的类容,我们验证下是什么?babel测试直达地址

    如何实现一个mini版的React(二)

    蓝色框框里面看到React.createElement收的第一个参数不是dom节点了,变成一个组件,第二个参数就是传入class组件里面的props。

    function createElement (tag, attrs, ...child) {
        return {
            tag, //组件
            attrs, // 组件的props
            child // 子节点或者子组件
        }
    }
    

    4、升级render,tag是否是函数

    • 创建组件
    • 设置组件的属性(props)
    function render( vnode ) {
    
        // ...
    	//如果是函数 ,则渲染组件
        if ( typeof vnode.tag === 'function' ) {
    
            //1 创建组件
            const component = createComponent( vnode.tag, vnode.attrs );
    
            //2 设置组件的属性(props)
            setComponentProps( component, vnode.attrs );
    
        } else {
          // ...
        }
        
      	 // ...
    }
    

    如上代码,我们需要在实现createComponentsetComponentProps 两个方法

    // 创建组件,返回的是一个组件的实例对象,组件内部如果是函数组件,则需要扩展成类组件
    function createComponent( component, props ) {
    
        let inst;
        // 如果是类定义组件,则直接返回实例
        if ( component.prototype && component.prototype.render ) {
            inst = new component( props );
        // 如果是函数定义组件,则将其扩展为类定义组件
        } else {
            inst = new Component( props );
            inst.constructor = component;
            inst.render = function() {
                return this.constructor( props );
            }
        }
    
        return inst;
    }
    

    setComponentProps方法

    function setComponentProps (comp, props, container) {
        comp.props = props;
        renderComponent(comp, container)
    }
    //导出,方便后面setState函数使用
    export function renderComponent (comp, container) {
        //真实dom
        const renderer = comp.render();
        console.log(renderer, container);
        render(renderer, container);
        comp._dom = container;
    }
    

    此时我们的组件就可以正常渲染出来了

    5、生命周期的实现

    setComponentProps 添加

    function setComponentProps (comp, props, container) {
        if(!comp._dom) {
            //实例对象的 constructor === 我们的class组件
            const fun = comp.constructor;
            if(fun.getDerivedStateFromProps) {
                fun.getDerivedStateFromProps(props, comp.state);
            }
        }
        //...
    }
    

    renderComponent 修改添加

    function renderComponent (comp, container) {
        if ( comp._dom ) {
            //comp 是react 类的实例。实例的constructor是就是react类
            const fun = comp.constructor; 
            if ( fun.getDerivedStateFromProps ) fun.getDerivedStateFromProps(comp.props, comp.state);
            if ( comp.shouldComponentUpdate ) {
               const isReload = comp.shouldComponentUpdate()
               if(!isReload) {
                    return null;
               }
            }
        }
        const renderer = comp.render();
        render(renderer, container);
        if(!comp._dom) {
            //在render后触发 componentDidMount
            if ( comp.componentDidMount ) comp.componentDidMount();
            // getSnapshotBeforeUpdate componentDidUpdate componentWillUnmount实现思路是和上面是一样
        }
        comp._dom = container;
    }
    ReactDom
    

    写个 Counter类传入传入到 render里面

    class Counter extends React.Component {
        constructor( props ) {
            super( props );
            this.state = {
                num: 0
            }
        }
    
        static getDerivedStateFromProps(nextProps, prevState) {
            console.log('getDerivedStateFromProps', nextProps, prevState);
            return null;
        }
    
    
        shouldComponentUpdate() {
            console.log( 'shouldComponentUpdate' );
            return true;
        }
    
    
        handerClick() {
            this.setState( { num: this.state.num + 1 } );
        }
    
        render() {
            return (
                 <div className="active"  id="test">
                    hello,<span>mini react</span> {this.state.num}
                    <button onClick={ () => {
                        this.handerClick();
                    }}>add</button>
                </div>
            );
        }
    
        componentDidMount () {
            console.log('组件挂载');
            const dom = document.getElementById('test')
            console.log('dom', dom);
            
        }
    }
    
    

    然后执行parcel index.html 此时页面的生命周期就可以渲染出来 如何实现一个mini版的React(二)

    5、结语

    感谢各位老铁,点赞加关注

    整理了下代码

    //react-dom/index.js
    function render (vnode, container) {
        if(typeof vnode.tag === 'function' ) {
        	//渲染组件
            _renderComp(vnode, container);
        }else {
        	//渲染虚拟dom
            const _dom = _renderVnode(vnode);
            container.appendChild(_dom);
        }
    }
    
    //render组件
    function _renderComp(vnode, container) {
        // 1:创建组件
        const comp = createComponent(vnode.tag, vnode.attrs)
        // 2: 设置组件属性
        setComponentProps(comp, vnode.attrs, vnode.tag);
        // 3: 组件渲染的节点对象返回
        container.appendChild(comp._dom);
    }
    
    //render虚拟dom
    function _renderVnode (vnode) {
        //如果虚拟dom不存在
        if(vnode === undefined || vnode === null || typeof vnode === 'boolean') {
            return '';
        };
    
        //如果vnode是字符串or数字
        if(typeof vnode === 'string' || typeof vnode === 'number' ) {
            //创建文本节点
            const textNode = document.createTextNode(vnode);
            return textNode
        }
    
         //否则虚拟DOM
         const { tag ,attrs} = vnode;
                
         //创建节点对象
         let dom = '';
         if(tag) {
             dom = document.createElement(tag)
            
         }
    
         if( attrs ){
             // 有属性值 key: className="active" 
             Object.keys(attrs).forEach( key => {
                 const value = attrs[key];
                 setAttribute(dom, key, value)
             })
         }
    
         //递归渲染子节点
         if(vnode.child) {
             vnode.child.forEach( c => render(c, dom))
         }
         
         return dom;
    }
    //创建组件
    function createComponent (comp, props) {
        let inst;
        // 如果是类定义组件,则直接返回实例, 此处暂不考虑hooks
        if ( comp.prototype && comp.prototype.render ) {
            inst = new comp( props );
        } else {
            // 如果是函数定义组件,则将其扩展为类定义组件
            inst = new React.Component( props );
            inst.constructor = comp;
            inst.render = function() {
                return this.constructor( props );
            }
        }
    
        return inst;
    }
    
    function setComponentProps (comp, props) {
        if(!comp._dom) {
            //实例对象的 constructor === 我们的class组件
            const fun = comp.constructor;
            if(fun.getDerivedStateFromProps) {
                fun.getDerivedStateFromProps(props, comp.state);
            }
        }
        comp.props = props;
        renderComponent(comp)
    }
    
    export function renderComponent (comp) {
        let _dom;
        if ( comp._dom ) {
            //comp 是react 类的实例。实例的constructor是就是react类
            const fun = comp.constructor; 
            if ( fun.getDerivedStateFromProps ) fun.getDerivedStateFromProps(comp.props, comp.state);
            if ( comp.shouldComponentUpdate ) {
               const isReload = comp.shouldComponentUpdate()
               if(!isReload) {
                    return null;
               }
            }
        }
        //获取虚拟dom
        const vnode = comp.render();
        console.log('vnode', vnode);
        _dom = _renderVnode(vnode);
    
        if ( comp._dom && comp._dom.parentNode ) {
            comp._dom.parentNode.replaceChild( _dom, comp._dom );
        }
    
        if(!comp._dom) {
            //在render后触发 componentDidMount
            if ( comp.componentDidMount ) comp.componentDidMount();
            // getSnapshotBeforeUpdate componentDidUpdate componentWillUnmount实现思路是和上面是一样
        }
        comp._dom = _dom;
    }
    
    
    
    //设置属性
    export function setAttribute(dom, key, value) {
        
        //将calssName 转成class
        if( key === 'className') {
            key = 'class'
        }
    
        //如果是事件 eg: onClick onBlur 
        if(/on\w/.test(key)) {
            //转小写
            key = key.toLowerCase();
            dom[key] = value || '';
        }
    
        if(key === 'style') {
            if(!value || typeof value === 'string') {
                //style 的值是字符串
                dom.style.cssText = value || ''
            } else if(value || typeof value === 'object'){
                //style 的值是对象
                for(let k in value) {
                    if(typeof value[k] === 'number') {
                        dom.style[key] = value[key] + 'px';
                    }else {
                        dom.style[key] = value[key];
                    }
                }
            }
        }else {
            //其他属性
            if( key in dom ) {
                // console.log('key', key, dom);
                dom[key] = value || ''
            }else if(value) {
                //更新值
                dom.setAttribute(key, value)
            }else {
                dom.removeAttribute(key)
            }
        }
    
        
    }
    
    const ReactDOM = {
        render
    }
    export default ReactDOM
    

    demo地址github

    下篇文章继续介绍剩下的内容:

    • React setState
    • React更新的时候diff算法

    起源地下载网 » 如何实现一个mini版的React(二)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元