Appearance
组件和生命周期
本节目标:理解“组件和生命周期”的核心思路,并能把它用于实际开发或面试表达。
**前言**
==在上一篇文章====JSX====和虚拟====DOM====中,我们实现了基础的====JSX====渲染功能,但是====React====的意义在于组件化。在这篇文章中,我们就要实现====React====的组件功能。==
==组件==
==React====定义组件的方式可以分为两种:函数和类,函数定义可以看做是类定义的一种简单形式。==
==createElement====的变化==
==回顾一下上一篇文章中我们对====React.createElement====的实现:==
==function== ==createElement====(== ==tag====,== ==attrs====,== ==...====children== ==) {====== ==return== =={====== ==tag,====== ==attrs,====== ==children====== ==}========}==
==这种实现我们前面暂时只用来渲染原生====DOM====元素,而对于组件,====createElement====得到的参数略有不同:==
==如果====JSX====片段中的某个元素是组件,那么====createElement====的第一个参数====tag====将会是一个方法,而不是字符串。==
==区分组件和原生====DOM====的工作,是====babel-plugin-transform-react-jsx====帮我们做的==
==例如在处理====<Welcome name="Sara" />====时,====createElement====方法的第一个参数====tag====,实际上就是我们定义====Welcome====的方法:==
==function== ==Welcome====(== ==props== ==) {====== ==return== ==<====h1====>Hello, {====props====.====name====}</====h1====>;========}==
==我们不需要对====createElement====做修改,只需要知道如果渲染的是组件,====tag====的值将是一个函数==
**组件基类****React.Component**
==通过类的方式定义组件,我们需要继承====React.Component====:==
==class== ==Welcome== ==extends== ==React====.====Component== =={====== ==render====() {====== ==return== ==<====h1====>Hello, {====this====.====props====.====name====}</====h1====>;====== ==}========}==
==所以我们就需要先来实现====React.Component====这个类:==
==Component==
==React.Component====包含了一些预先定义好的变量和方法,我们来一步一步地实现它:==
==先定义一个====Component====类:==
==class== ==Component== =={}==
**state & props**
==通过继承====React.Component====定义的组件有自己的私有状态====state====,可以通过====this.state====获取到。同时也能通过====this.props====来获取传入的数据。==
==所以在构造函数中,我们需要初始化====state====和====props==
==// React.Component========class== ==Component== =={====== ==constructor====(== ==props== ===== =={} ) {====== ==this====.====state== ===== =={};====== ==this====.====props== ===== ==props;====== ==}========}==
==setState==
==组件内部的====state====和渲染结果相关,当====state====改变时通常会触发渲染,为了让====React====知道我们改变了====state====,我们只能通过====setState====方法去修改数据。我们可以通过====Object.assign====来做一个简单的实现。==
==在每次更新====state====后,我们需要调用====renderComponent====方法来重新渲染组件,====renderComponent====方法的实现后文会讲到。==
==import== =={== ==renderComponent== ==}== ==from== =='../react-dom/render'========class== ==Component== =={====== ==constructor====(== ==props== ===== =={} ) {====== ==// ...====== ==}======
==setState====(== ==stateChange== ==) {====== ==//== ==将修改合并到====state====== ==Object====.====assign====(== ==this====.====state====, stateChange );====== ==renderComponent====(== ==this== ==);====== ==}========}==
==你可能听说过====React====的====setState====是异步的,同时它有很多优化手段,这里我们暂时不去管它,在以后会有一篇文章专门来讲====setState====方法。==
==render==
==上一篇文章中实现的====render====方法只支持渲染原生====DOM====元素,我们需要修改====ReactDOM.render====方法,让其支持渲染组件。==
==修改之前我们先来回顾一下上一篇文章中我们对====ReactDOM.render====的实现:==
==function== ==render====(== ==vnode====,== ==container== ==) {====== ==return== ==container====.====appendChild====(== ==_render====( vnode ) );========}======
==function== ==_render====(== ==vnode== ==) {======
==if== ==( vnode== ======= ==undefined== ==||== ==vnode== ======= ==null== ==||== ==typeof== ==vnode== ======= =='boolean'== ==) vnode== ===== ==''====;======
==if== ==(== ==typeof== ==vnode== ======= =='number'== ==) vnode== ===== ==String====( vnode );======
==if== ==(== ==typeof== ==vnode== ======= =='string'== ==) {====== ==let== ==textNode== ===== ==document====.====createTextNode====( vnode );====== ==return== ==textNode;====== ==}======
==const== ==dom== ===== ==document====.====createElement====(== ==vnode====.====tag== ==);======
==if== ==(== ==vnode====.====attrs== ==) {====== ==Object====.====keys====(== ==vnode====.====attrs== ==).====forEach====(== ==key== ===>== =={====== ==const== ==value== ===== ==vnode====.====attrs====[ key ];====== ==setAttribute====( dom, key, value );====== ==} );====== ==}======
==vnode====.====children====.====forEach====(== ==child== ===>== ==render====( child, dom ) );== ==//== ==递归渲染子节点======
==return== ==dom;== ======}==
==我们需要在其中加一段用来渲染组件的代码:==
==function== ==_render====(== ==vnode== ==) {======
==// ...======
==if== ==(== ==typeof== ==vnode====.====tag== ======= =='function'== ==) {======
==const== ==component== ===== ==createComponent====(== ==vnode====.====tag====,== ==vnode====.====attrs== ==);======
==setComponentProps====( component,== ==vnode====.====attrs== ==);======
==return== ==component====.====base====;====== ==}====== ==== ==// ...========}==
==组件渲染和生命周期==
==在上面的方法中用到了====createComponent====和====setComponentProps====两个方法,组件的生命周期方法也会在这里面实现。==
==生命周期方法是一些在特殊时机执行的函数,例如====componentDidMount====方法会在组件挂载后执行==
==createComponent====方法用来创建组件实例,并且将函数定义组件扩展为类定义组件进行处理,以免其他地方需要区分不同定义方式。==
==//== ==创建组件========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====方法用来更新====props====,在其中可以实现====componentWillMount====,====componentWillReceiveProps====两个生命周期方法==
==// set props========function== ==setComponentProps====(== ==component====,== ==props== ==) {======
==if== ==(== ==!====component====.====base== ==) {====== ==if== ==(== ==component====.====componentWillMount== ==)== ==component====.====componentWillMount====();====== ==}== ==else== ==if== ==(== ==component====.====componentWillReceiveProps== ==) {====== ==component====.====componentWillReceiveProps====( props );====== ==}======
==component====.====props== ===== ==props;======
==renderComponent====( component );======
==}==
==renderComponent====方法用来渲染组件,====setState====方法中会直接调用这个方法进行重新渲染,在这个方法里可以实现====componentWillUpdate====,====componentDidUpdate====,====componentDidMount====几个生命周期方法。==
==export== ==function== ==renderComponent====(== ==component== ==) {======
==let== ==base;======
==const== ==renderer== ===== ==component====.====render====();======
==if== ==(== ==component====.====base== ==&&== ==component====.====componentWillUpdate== ==) {====== ==component====.====componentWillUpdate====();====== ==}======
==base== ===== ==_render====( renderer );======
==if== ==(== ==component====.====base== ==) {====== ==if== ==(== ==component====.====componentDidUpdate== ==)== ==component====.====componentDidUpdate====();====== ==}== ==else== ==if== ==(== ==component====.====componentDidMount== ==) {====== ==component====.====componentDidMount====();====== ==}======
==if== ==(== ==component====.====base== ==&&== ==component====.====base====.====parentNode== ==) {====== ==component====.====base====.====parentNode====.====replaceChild====( base,== ==component====.====base== ==);====== ==}======
==component====.====base== ===== ==base;====== ==base====.====_component== ===== ==component;======
==}==
==渲染组件==
==现在大部分工作已经完成,我们可以用它来渲染组件了。==
==渲染函数定义组件==
==渲染前文提到的====Welcome====组件:==
==const== ==element== ===== ==<====Welcome== ==name========="Sara"== ==/>;========ReactDOM====.====render====(====== ==element====,====== ==document====.====getElementById====(== =='root'== ==)========);==
==在浏览器中可以看到结果:==
==试试更复杂的例子,将多个组件组合起来:==
==function== ==App====() {====== ==return== ==(====== ==<====div====>====== ==<====Welcome== ==name========="Sara"== ==/>====== ==<====Welcome== ==name========="Cahal"== ==/>====== ==<====Welcome== ==name========="Edite"== ==/>====== ==</====div====>====== ==);========}========ReactDOM====.====render====(====== ==<====App== ==/>,====== ==document====.====getElementById====(== =='root'== ==)========);==
==在浏览器中可以看到结果:==
==渲染类定义组件==
==我们来试一试将刚才函数定义组件改成类定义:==
==class== ==Welcome== ==extends== ==React====.====Component== =={====== ==render====() {====== ==return== ==<====h1====>Hello, {====this====.====props====.====name====}</====h1====>;====== ==}========}======
==class== ==App== ==extends== ==React====.====Component== =={====== ==render====() {====== ==return== ==(====== ==<====div====>====== ==<====Welcome== ==name========="Sara"== ==/>====== ==<====Welcome== ==name========="Cahal"== ==/>====== ==<====Welcome== ==name========="Edite"== ==/>====== ==</====div====>====== ==);====== ==}========}========ReactDOM====.====render====(====== ==<====App== ==/>,====== ==document====.====getElementById====(== =='root'== ==)========);==
==运行起来结果和函数定义组件完全一致:==
==再来尝试一个能体现出类定义组件区别的例子,实现一个计数器====Counter====,每点击一次就会加====1====。==
==并且组件中还增加了两个生命周期函数:==
==class== ==Counter== ==extends== ==React====.====Component== =={====== ==constructor====(== ==props== ==) {====== ==super====(== ==props== ==);====== ==this====.====state== ===== =={====== ==num====:== ==0====== ==}====== ==}======
==componentWillUpdate====() {====== ==console====.====log====(== =='update'== ==);====== ==}======
==componentWillMount====() {====== ==console====.====log====(== =='mount'== ==);====== ==}======
==onClick====() {====== ==this====.====setState====( {== ==num====:== ==this====.====state====.====num== ==+== ==1== ==} );====== ==}======
==render====() {====== ==return== ==(====== ==<====div== ==onClick========={ ()== ===>== ==this====.====onClick====() }>====== ==<====h1====>number: {====this====.====state====.====num====}</====h1====>====== ==<====button====>add</====button====>====== ==</====div====>====== ==);====== ==}========}======
==ReactDOM====.====render====(====== ==<====Counter== ==/>,====== ==document====.====getElementById====(== =='root'== ==)========);==
==可以看到结果:==
==mount====只在挂载时输出了一次,后面每次更新时会输出====update==
==后话==
==至此我们已经从====API====层面实现了====React====的核心功能。但是我们目前的做法是每次更新都重新渲染整个组件甚至是整个应用,这样的做法在页面复杂时将会暴露出性能上的问题,====DOM====操作非常昂贵,而为了减少====DOM====操作,====React====又做了哪些事?这就是我们下一篇文章的内容了。==> 来自
<https://github.com/hujiulong/blog/issues/5>