【React】每个人都可以理解的合成事件与setState机制

这是一篇给小白的,极为通俗易懂的个人理解总结。

当年初学 react,学习 setState 机制,总是被一堆源码中的函数名搞的一头雾水。

这里没有任何源码,只有经过个人理解,总结出来的简易流程。

希望能够帮助到,和曾经的自己(现在也差不多),一样想要精进、却举步维艰的你们~


我们从一道常见的面试题入手:

react 的合成事件,是如何映射到真实 dom 上的?


我们以一个简版的例子,看一下从用户点击动作开始,都发生了什么~


定义一个container:App

import React, { Component } from 'react'

class App extends Component {

  constructor(props) {

    super(props)

    this.state = {

      count: 0,

    }

    this.clickHandler = this.clickHandler.bind(this)

  }

  clickHandler() {

    console.log('count1:', this.state.count)

    this.setState({

      count: 1,

    })

    console.log('count2:', this.state.count)

    this.setState({

      count: 2,

    })

    console.log('count3:', this.state.count)

  }

  render() {

    console.log('count render', this.state.count)

    return <button onClick={this.clickHandler}>点我更新count</button>

  }

}

export default App


旅程开始~


1.前言:元素挂载

点击元素前,得先有元素。so,先简单看一下元素挂载过程~

ReactDOM.render() 方法,解析 jsx 生成 virtual dom tree(React16中,又由 virtual dom tree 生成 fiber tree),最终将真实的节点渲染到页面上。

在这个过程中,react 内部,会在每个真实的 dom 元素上,偷偷地添加一些属性,用来配合它搞一些事情。

其中,除了root根节点,其他所有元素,都被添加了这么个属性:__reactEventHandlers$*******

这里,我们直接读取了 button 元素的这个属性。

似乎有点剧透了,咳咳~  ┓( ´∀` )┏

下面,进入正题👇


2.事件触发

react 基于事件冒泡,统一在 document 上插入了原生的事件监听方法,用于捕获页面任意位置的用户操作。

【注】

经实测,这里给 document 插入的原生事件监听,取决于子元素设置了什么合成事件监听。

每个合成事件都有对应的原生事件,以此给 document 添加上需要的原生事件监听函数。


当用户点击 button,click 事件顺着 dom 树结构冒泡到 document 上,document 上的原生 onclick 事件响应函数被触发。

这个响应函数做了什么事呢?

大概是这样:

function documentClickHandler(e) {

    // 获取真正触发点击事件的元素节点

    const target = e.target

    // 执行元素节点上注册的合成事件响应函数

    target.__reactEventHandlers$*******.onClick()

}

ok,事情的本质,其实就是这么简单。

到这里,那个面试题的答案,已经很清晰了。

但是整个流程并没有结束,我们继续,看看 setState 这个小家伙,一会儿同步、一会儿异步,到底是在干啥。


3.执行合成事件响应函数:clickHandler

 clickHandler() {

    console.log('count1:', this.state.count)

    this.setState({

      count: 1,

    })

    console.log('count2:', this.state.count)

    this.setState({

      count: 2,

    })

    console.log('count3:', this.state.count)

  }

这里,连续调用了两次 setState。但是,打印出的 count1、count2、count3,全部为 0。

这就是典型的,所谓 setState 的异步现象:调用 setState 后,state 的值并没有立即更新。


4.合成事件中的 setState

以下代码,由个人对这个流程的简化理解而来,全部是伪代码。并非由源码精简而来。

这里的简单例子,只为简单说明整体逻辑流程。

如有理解偏差,烦请指正。

let flag = false // 相当于源码的 isBatchingUpdates

function documentClickHandler(e) {

    // 获取真正触发点击事件的元素节点

    const target = e.target

    // 设置标记

    flag = true

    // 执行元素节点上注册的合成事件响应函数

    target.__reactEventHandlers$*******.onClick()

    // 置回标记

    flag = false

    // 触发 setState,批量更新在 onClick 中缓存的那些 state

    setState()

}


const arr = [ ]   // 相当于源码的 dirtyComponents


// 开发者调用的setState,也是它👇

function setState(state) {

    // 如果标记是 true,就先不做更新。缓存当前 state,等到 flag 为 false 的时候,做批量更新

    if (flag) {

        arr.push(state)

    } else {

        // 这里,没有传 state,就说明是合成事件执行完,调用进来的。批量更新缓存的那些 state

        if (!state) {

             let stateObj = {}

              arr.forEach(state => {

                     stateObj = Object.assign(stateObj, state)

              })

            // 真正执行 setState(我臆想的方法。。总之应该会有个类似的东西。。)

            doSetState(stateObj)

        } else {          

            doSetState(state)

        }

    }

}

【注】

react 生命周期中的 setState,同理。

比如 componentDidMount 中连续调用了两次 setState,在组件挂载过程中,需要执行 componentDidMount 这个生命周期时:

doRender() {

    // 做各种挂载需要的逻辑

    // ......    

    // 挂载完成,执行生命周期函数。和合成事件的执行同理,用 flag 标记,告诉 setState 走批量更新

    flag = true

    componentDidMount()    

    flag = false

}


ok,旅程结束~

如有理解偏差,请大佬们指教。感谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352