前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解React

深入理解React

作者头像
尹光耀
发布2021-10-19 09:51:59
5990
发布2021-10-19 09:51:59
举报
文章被收录于专栏:前端小馆前端小馆前端小馆

对于常用的库和框架,如果仅限于会用,我觉得还是远远不够,至少要理解它的思想,这样才知道怎么可以发挥最大威力,这篇文章是看了react-lite源码后写的。

createElement和component

在react里面,经过babel的解析后,jsx会变成createElement执行后的结果。

const Test = (props) => <h1>hello, {props.name}</h1>;
<Test name="world" />

<Test name="world" />经过babel解析后会变为createElement(Test, {name: "world}),这里的Test就是上面的Test方法,name就是Test方法里面接受的props中的name。 实际上当我们从开始加载到渲染的时候做了下面几步:

// 1. babel解析jsx
<Test name="world"> -> createElement(Test, {name: "world"})
// 2. 对组件进行处理
const props = {name: world};
const newTest = new Test(props);
// 3. 执行render方法
newTest.render(); // 类组件
Test(props); // 函数组件

这样也很容易理解,const Test = <div>hello, world</div>和const Test = () => <div>hello, world</div>的区别了。

key

react中的diff会根据子组件的key来对比前后两次virtual dom(即使前后两次子组件顺序打乱),所以这里的key最好使用不会变化的值,比如id之类的,最好别用index,如果有两个子组件互换了位置,那么index改变就会导致diff失效。

cloneElement

原来对cloneElement的理解就是类似cloneElement(App, {})这种写法,现在看了实现之后才理解。原来第一个参数应该是一个reactElement,而不是一个reactComponent,应该是<App />,而不是App,这个也确实是我没有好好看文档。

短路操作符判断

为什么布尔类型和null类型的值可以这么写,而数字类型却不行?

showLoading && <Loading />

如果showLoading是个数字0,那么最后渲染出来的居然是个0,但是showLoading是个false或者null,最后就什么都不渲染,这个是为什么? 首先上述代码会被babel编译为如下格式:

showLoading && React.createElement(Loading, null)

而如果showLoading是false或者0的时候,就会短路掉后面的组件,最后渲染出来的应该是个showLoading。 但是react-lite在渲染子组件的时候(递归渲染虚拟dom),会判断当前是否为布尔类型和null,如果是布尔类型或者null,则会被直接过滤掉。

function collectChild(child, children) {
    if (child != null && typeof child !== 'boolean') {
        if (!child.vtype) {
            // convert immutablejs data
            if (child.toJS) {
                child = child.toJS()
                if (_.isArr(child)) {
                    _.flatEach(child, collectChild, children)
                } else {
                    collectChild(child, children)
                }
                return
            }
            child = '' + child
        }
        children[children.length] = child
    }
}

shouldComponentUpdate

当shouldComponentUpdate返回false的时候,组件没有重新渲染,但是更新后的state和props已经挂载到了组件上面,这个时候如果打印state和props,会发现拿到的已经是更新后的了。

setState

react里面setState后不会立即更新,但在某些场景下也会立即更新,下面这几种情况打印的值你都能回答的上来吗?

class App extends React.Component {
    state = {
        count: 0;
    }
    test() {
        this.setState({
            count: this.state.count + 1
        }); 
        console.log(this.state.count); // 此时为0
        this.setState({
            count: this.state.count + 1
        });
        console.log(this.state.count); // 此时为0
    }
    test2() {
        setTimeout(() => {
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count); // 此时为1
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count); // 此时为2
        })
    }
    test3() {
        Promise.resolve().then(() => {
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count); // 此时为1
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count); // 此时为2
        })
    }
    test4() {
        this.setState(prevState => {
            console.log(prevState.count); // 0
        return {
            count: prevState.count + 1
        };
        });
        this.setState(prevState => {
            console.log(prevState.count); // 1
            return {
                count: prevState.count + 1
            };
        });
    }
    async test4() {
        await 0;
        this.setState({
            count: this.state.count + 1
        });
        console.log(this.state.count); // 此时为1
        this.setState({
            count: this.state.count + 1
        });
        console.log(this.state.count); // 此时为2
    }
}

在react中为了防止多次setState导致多次渲染带来不必要的性能开销,会将待更新的state放到队列中,等到合适的时机(生命周期钩子和事件)后进行batchUpdate,所以在setState后无法立即拿到更新后的state。

因此很多人说setState是异步的,setState表现确实是异步,但是里面没有用异步代码实现。update不是等主线程代码执行结束后才执行的,而是需要手动触发。 如果是给setState传入一个函数,这个函数是执行前一个setState后才被调用的,所以函数返回的参数可以拿到更新后的state。 但是如果将setState在异步方法中(setTimeout、Promise等等)调用,由于这些方法是异步的,会导致生命周期钩子或者事件方法先执行,执行完这些后会将更新队列的pending状态置为false,这个时候在执行setState后会导致组件立即更新。从这里也能说明setState本质并不是异步的,只是模拟了异步的表现。

ref

ref用到原生的标签上,可以直接在组件内部用http://this.refs.xxx的方法获取到真实DOM。 ref用到组件上,需要用ReactDOM.findDOMNode(http://this.refs.xxx)的方式来获取到这个组件对应的DOM节点,http://this.refs.xxx获取到的是虚拟DOM。

合成事件

react里面将可以冒泡的事件委托到了document上,通过向上遍历父节点模拟了冒泡的机制。 比如当触发onClick事件时,会先执行target元素的onClick事件回调函数,如果回调函数里面阻止了冒泡,就不会继续向上查找父元素。否则,就会继续向上查找父元素,并执行其onClick的回调函数。 当跳出循环的时候,就会开始进行组件的批量更新(如果没有收到新的props或者state队列为空就不会进行更新)。

本文如果有错误之处,希望能够指出,欢迎大家一起探讨。

参考:

  1. react-lite
  2. 从零开始实现一个react
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • createElement和component
  • key
  • cloneElement
  • 短路操作符判断
  • shouldComponentUpdate
  • setState
  • ref
  • 合成事件
  • 参考:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档