成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

preact源碼分析,有毒

zhangwang / 1376人閱讀

摘要:最近讀了讀源碼,記錄點(diǎn)筆記,這里采用例子的形式,把代碼的執(zhí)行過程帶到源碼里走一遍,順便說明一些重要的點(diǎn)建議對著源碼看和虛擬結(jié)點(diǎn)是對真實(shí)元素的一個(gè)對象表示由創(chuàng)建方法在根據(jù)指定結(jié)點(diǎn)名稱屬性子節(jié)點(diǎn)來創(chuàng)建之前會對子節(jié)點(diǎn)進(jìn)行處理,包括當(dāng)前要創(chuàng)建的不是

最近讀了讀preact源碼,記錄點(diǎn)筆記,這里采用例子的形式,把代碼的執(zhí)行過程帶到源碼里走一遍,順便說明一些重要的點(diǎn),建議對著preact源碼看
vnode和h()

虛擬結(jié)點(diǎn)是對真實(shí)DOM元素的一個(gè)js對象表示,由h()創(chuàng)建

h()方法在根據(jù)指定結(jié)點(diǎn)名稱、屬性、子節(jié)點(diǎn)來創(chuàng)建vnode之前,會對子節(jié)點(diǎn)進(jìn)行處理,包括

當(dāng)前要創(chuàng)建的vnode不是組件,而是普通標(biāo)簽的話,文本子節(jié)點(diǎn)是null,undefined,轉(zhuǎn)成"",文本子節(jié)點(diǎn)是number類型,轉(zhuǎn)成字符串

連續(xù)相鄰的兩個(gè)子節(jié)點(diǎn)都是文本結(jié)點(diǎn),合并成一個(gè)

例如:

h("div",{ id: "foo", name : "bar" },[
            h("p",null,"test1"),
            "hello",
            null
            "world", 
            h("p",null,"test2")
        ]
)

對應(yīng)的vnode={

    nodeName:"div",
    attributes:{
        id:"foo",
        name:"bar"
    },
    [
        {
            nodeName:"p",
            children:["test1"]
        },
        "hello world",
        {
            nodeName:"p",
            children:["test2"]
        }
    ]

}
render()

render()就是react中的ReactDOM.render(vnode,parent,merge),將一個(gè)vnode轉(zhuǎn)換成真實(shí)DOM,插入到parent中,只有一句話,重點(diǎn)在diff中

return diff(merge, vnode, {}, false, parent, false);
diff

diff主要做三件事

調(diào)用idff()生成真實(shí)DOM

掛載dom

在組件及所有子節(jié)點(diǎn)diff完成后,統(tǒng)一執(zhí)行收集到的組件的componentDidMount()

重點(diǎn)看idiff

idiff(dom,vnode)處理vnode的三種情況

vnode是一個(gè)js基本類型值,直接替換dom的文本或dom不存在,根據(jù)vnode創(chuàng)建新的文本返回

vnode.nodeName是function 即當(dāng)前vnode表示一個(gè)組件

vnode.nodeName是string 即當(dāng)前vnode表示一個(gè)對普通html元素的js表示

一般我們寫react應(yīng)用,最外層有一個(gè)類似的組件,渲染時(shí)ReactDOM.render(>,root),這時(shí)候diff走的就是第二步,根據(jù)vnode.nodeName==="function"來構(gòu)建組件,執(zhí)行buildComponentFromVNode(),實(shí)例化組件,子組件等等

第三種情況一般出現(xiàn)在組件的定義是以普通標(biāo)簽包裹的,組件內(nèi)部狀態(tài)發(fā)生改變了或者初次實(shí)例化時(shí),要render組件了,此時(shí),要將當(dāng)前組件現(xiàn)有的dom與執(zhí)行compoent.render()方法得到的新的vnode進(jìn)行Diff,來決定當(dāng)前組件要怎么更新DOM

class Comp1 extends Component{

    render(){
        return 
{ list.map(x=>{ return

{x.txt}

}) }
} //而不是 //render(){ // return //} }
普通標(biāo)簽元素及子節(jié)點(diǎn)的diff

我們以一個(gè)真實(shí)的組件的渲染過程來對照著走一下表示普通dom及子節(jié)點(diǎn)的vnode和真實(shí)dom之間的diff過程

假設(shè)現(xiàn)在有這樣一個(gè)組件

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      change: false,
      data: [1, 2, 3, 4]
    };
  }

 change(){
    this.setState(preState => {
        return {
            change: !preState.change,
            data: [11, 22, 33, 44]
        };
    });
 }

  render(props) {
    const { data, change } = this.state;
    return (
      
{data.map((x, index) => { if (index == 2 && this.state.change) { return

{x}

; } return

{x}

; })} {!change ?

hello world

: null}
); } }
初次渲染

App組件初次掛載后的DOM結(jié)構(gòu)大致表示為

dom = {
       tageName:"DIV",
       childNodes:[
           
           

1

,

2

,

3

,

4

,

hello world

] }
更新

點(diǎn)擊一下按鈕,觸發(fā)setState,狀態(tài)發(fā)生變化,App組件實(shí)例入渲染隊(duì)列,一段時(shí)間后(異步的),渲染隊(duì)列中的組件被渲染,實(shí)例.render執(zhí)行,此時(shí)生成的vnode結(jié)構(gòu)大致是

vnode= {
    nodeName:"div"
    children:[
        { nodeName:"button", children:["change"] },
        { nodeName:"p", attributes:{key:"0"}, children:[11]},
        { nodeName:"p", attributes:{key:"1"}, children:[22]},
         { nodeName:"h2", attributes:{key:"2"}, children:[33]},
        { nodeName:"p", attributes:{key:"3"}, children:[44]},
    ]
 }

//少了最后的h1元素,第三個(gè)p元素變成了h2

然后在renderComponent方法內(nèi)diff上面的dom和vnode diff(dom,vnode),此時(shí)在diff內(nèi)部調(diào)用的idff方法內(nèi),執(zhí)行的就是上面說的第三種情況vnode.nodeType是普通標(biāo)簽,關(guān)于renderComponent后面介紹

首先dom和vnode標(biāo)簽名是一樣的,都是div(如果不一樣,要通過vnode.nodeName來創(chuàng)建一個(gè)新元素,并把dom子節(jié)點(diǎn)復(fù)制到這個(gè)新元素下),并且vnode有多個(gè)children,所以直接進(jìn)入innerDiffNode(dom,vnode.children)函數(shù)

innerDiffNode(dom,vchildren)工作流程

對dom結(jié)點(diǎn)下的子節(jié)點(diǎn)遍歷,根據(jù)是否有key,放入兩個(gè)數(shù)組keyed和children(那些沒有key放到這個(gè)里)

遍歷vchildren,為當(dāng)前的vchild找一個(gè)相對應(yīng)的dom下的子節(jié)點(diǎn)child,例如,key一樣的,如果vchild沒有key,就從children數(shù)組中找標(biāo)簽名一樣的

child=idiff(child, vchild); 遞歸diff,根據(jù)vchild來得到處理后的child,將child應(yīng)用到當(dāng)前父元素dom下

接著看上面的例子

dom子節(jié)點(diǎn)遍歷 得到兩個(gè)數(shù)組

keyed=[
    

1

,

2

,

3

,

4

] children=[ ,

hello world

]

迭代vnode的children數(shù)組

存在key相等的

vchild={ nodeName:"p", attributes:{key:"0"}, children:[11]},
child=keyed[0]=

1

存在標(biāo)簽名改變的

vchild={ nodeName:"h2", attributes:{key:"2"}, children:[33]},
child=keyed[2]=

3

,

存在標(biāo)簽名相等的

vchild={ nodeName:"button", children:["change"] },
child=,

然后對vchild和child進(jìn)行diff

child=idff(child,vchild)

看一組子元素的更新

看上面那組存在keys相等的子元素的diff,vchild.nodeName=="p"是個(gè)普通標(biāo)簽,所以還是走的idff內(nèi)的第三種情況。

但這里vchild只有一個(gè)后代元素,并且child只有一個(gè)文本結(jié)點(diǎn),可以明確是文本替換的情況,源碼中這樣處理,而不是進(jìn)入innerDiffNode,算是一點(diǎn)優(yōu)化

let fc = out.firstChild,
        props = out[ATTR_KEY],
        vchildren = vnode.children;

    if (props == null) {
        props = out[ATTR_KEY] = {};
        for (let a = out.attributes, i = a.length; i--;) props[a[i].name] = a[i].value;
    }

    // Optimization: fast-path for elements containing a single TextNode:
    if (!hydrating && vchildren && vchildren.length === 1 && typeof vchildren[0] === "string" && fc != null && fc.splitText !== undefined && fc.nextSibling == null) {
        if (fc.nodeValue != vchildren[0]) {
            fc.nodeValue = vchildren[0];
        }
    }

所有執(zhí)行child=idiff(child,vchild)

child=

11

//文本值更新了

然后將這個(gè)child放入當(dāng)前dom下的合適位置,一個(gè)子元素的更新就完成了

如果vchild.children數(shù)組有多個(gè)元素,又會進(jìn)行vchild的子元素的迭代diff

至此,diff算是說了一半了,另一半是vnode表示一個(gè)組件的情況,進(jìn)行組件渲染或更新diff

組件的渲染、diff與更新

和組件的渲染,diff相關(guān)的方法主要有三個(gè),依次調(diào)用關(guān)系

buildComponentFromVNode

組件之前沒有實(shí)例化過,實(shí)例化組件,為組件應(yīng)用props,setComponentProps()

組件已經(jīng)實(shí)例化過,屬于更新階段,setComponentProps()

setComponentProps

在setComponentProps(compInst)內(nèi)部進(jìn)行兩件事

根據(jù)當(dāng)前組件實(shí)例是首次實(shí)例化還是更新屬性來調(diào)用組件的componentWillMount或者componentWillReceiveProps

判斷是否時(shí)強(qiáng)制渲染,renderComponent()或者把組件入渲染隊(duì)列,異步渲染

renderComponent

renderComponent內(nèi)會做這些事:

判斷組件是否更新,更新的話執(zhí)行componentWillUpdate(),

判斷shouldComponentUpdate()的結(jié)果,決定是否跳過執(zhí)行組件的render方法

需要render,執(zhí)行組件render(),返回一個(gè)vnode,diff當(dāng)前組件表示的頁面結(jié)構(gòu)上的真實(shí)DOM和返回的這個(gè)vnode,應(yīng)用更新.(像上面說明的那個(gè)例子一樣)

依然從例子入手,假設(shè)現(xiàn)在有這樣一個(gè)組件

class Welcom extends Component{

    render(props){
        return 

{props.text}

} } class App extends Component { constructor(props){ super(props) this.state={ text:"hello world" } } change(){ this.setState({ text:"now changed" }) } render(props){ return

preact

} } render(,root) vnode={ nodeName:App, }

首次render

render(,root)執(zhí)行,進(jìn)入diff(),vnode.nodeName==App,進(jìn)入buildComponentFromVNode(null,vnode)

程序首次執(zhí)行,頁面還沒有dom結(jié)構(gòu),所以此時(shí)buildComponentFromVNode第一個(gè)參數(shù)是null,進(jìn)入實(shí)例化App組件階段

c = createComponent(vnode.nodeName, props, context);
if (dom && !c.nextBase) {
    c.nextBase = dom;
    // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229:
    oldDom = null;
}
setComponentProps(c, props, SYNC_RENDER, context, mountAll);
dom = c.base;

在setComponentProps中,執(zhí)行component.componentWillMount(),組件入異步渲染隊(duì)列,在一段時(shí)間后,組件渲染,執(zhí)行
renderComponent()

rendered = component.render(props, state, context);

根據(jù)上面的定義,這里有

rendered={
    nodeName:"div",
    children:[
        {
            nodeName:"button",
            children:["change"]
        },
        {
            nodeName:"h1",
            children:["preact"]
        },{
            nodeName:Welcom,
            attributes:{
                text:"hello world"
            }
        }
    ]
}

nodeName是普通標(biāo)簽,所以執(zhí)行

base = diff(null, rendered) 
//這里需要注意的是,renderd有一個(gè)組件child,所以在diff()-->idiff()[**走第三種情況**]---->innerDiffNode()中,對這個(gè)組件child進(jìn)行idiff()時(shí),因?yàn)槭墙M件,所以走第二種情況,進(jìn)入buildComponentFromVNode,相同的流程

component.base=base //這里的baes是vnode diff完成后生成的真實(shí)dom結(jié)構(gòu),組件實(shí)例上有個(gè)base屬性,指向這個(gè)dom

base大體表示為

base={
    tageName:"DIV",
       childNodes:[
        
           

preact

hello world

] } 然后為當(dāng)前dom元素添加一些組件的信息 base._component = component; base._componentConstructor = component.constructor;

至此,初始化的這次組件渲染就差不多了,buildComponentFromVNode返回dom,即實(shí)例化的App的c.base,在diff()中將dom插入頁面

更新

然后現(xiàn)在點(diǎn)擊按鈕,setState()更新狀態(tài),setState源碼中

let s = this.state;
if (!this.prevState) this.prevState = extend({}, s);
extend(s, typeof state==="function" ? state(s, this.props) : state);
/**
* _renderCallbacks保存回調(diào)列表
*/
if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
enqueueRender(this);

組件入隊(duì)列了,延遲后執(zhí)行renderComponent()

這次,在renderComponent中,因?yàn)楫?dāng)前App的實(shí)例已經(jīng)有一個(gè)base屬性,所以此時(shí)實(shí)例屬于更新階段isUpdate = component.base =true,執(zhí)行實(shí)例的componentWillUpdate()方法,如果實(shí)例的shouldComponentUpdate()返回true,實(shí)例進(jìn)入render階段。

這時(shí)候根據(jù)新的props,state

rendered = component.render(props, state, context);

rendered={
    nodeName:"div",
    children:[
        {
            nodeName:"button",
            children:["change"]
        },
        {
            nodeName:"h1",
            children:["preact"]
        },{
            nodeName:Welcom,
            attributes:{
                text:"now changed" //這里變化
            }
        }
    ]
}

然后,像第一次render一樣,base = diff(cbase, rendered),但這時(shí)候,cbase是上一次render后產(chǎn)生的dom,即實(shí)例.base,然后頁面引用更新后的新的dom.rendered的那個(gè)組件子元素(Welcom)同樣執(zhí)行一次更新過程,進(jìn)入buildComponentFromVNode(),走一遍buildComponentFromVNode()-->setComponentProps()--->renderComponent()--->render()--->diff(),直到數(shù)據(jù)更新完畢

總結(jié)

preact src下只有15個(gè)js文件,但一篇文章不能覆蓋所有點(diǎn),這里只是記錄了一些主要的流程,最后放一張有毒的圖

github

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/93440.html

相關(guān)文章

  • 幫你讀懂preact源碼(一)

    摘要:是一個(gè)最小的庫,但由于其對尺寸的追求,它的很多代碼可讀性比較差,市面上也很少有全面且詳細(xì)介紹的文章,本篇文章希望能幫助你學(xué)習(xí)的源碼。建議與源碼一起閱讀本文。 作為一名前端,我們需要深入學(xué)習(xí)react的運(yùn)行機(jī)制,但是react源碼量已經(jīng)相當(dāng)龐大,從學(xué)習(xí)的角度,性價(jià)比不高,所以學(xué)習(xí)一個(gè)react mini庫是一個(gè)深入學(xué)習(xí)react的一個(gè)不錯(cuò)的方法。 preact是一個(gè)最小的react mi...

    XboxYan 評論0 收藏0
  • Preact了解一個(gè)類React的框架是怎么實(shí)現(xiàn)的(二): 元素diff

    摘要:本系列文章將重點(diǎn)分析類似于的這類框架是如何實(shí)現(xiàn)的,歡迎大家關(guān)注和討論。作為一個(gè)極度精簡的庫,函數(shù)是屬于本身的。 前言   首先歡迎大家關(guān)注我的掘金賬號和Github博客,也算是對我的一點(diǎn)鼓勵(lì),畢竟寫東西沒法獲得變現(xiàn),能堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。  之前分享過幾篇關(guān)于React的文章: React技術(shù)內(nèi)幕: key帶來了什么 React技術(shù)內(nèi)幕: setState的秘密...

    張巨偉 評論0 收藏0
  • 幫你讀懂preact源碼(三)

    摘要:對回收的處理在中,回收調(diào)用了兩個(gè)方法,節(jié)點(diǎn)的回收一般會調(diào)用,組件的回收會調(diào)用。個(gè)人理解從以上源碼閱讀中我們可以看到,最大的性能問題在于遞歸的,中的與也是為了緩解這個(gè)問題。為不同類型的更新分配優(yōu)先級。 對回收的處理 在preact中,回收調(diào)用了兩個(gè)方法,dom節(jié)點(diǎn)的回收一般會調(diào)用recollectNodeTree,組件的回收會調(diào)用unmountComponent。 preact復(fù)用dom...

    yuanxin 評論0 收藏0
  • 幫你讀懂preact源碼(二)

    摘要:最后刪除新的樹中不存在的節(jié)點(diǎn)。而中會記錄對其做了相應(yīng)的優(yōu)化,節(jié)點(diǎn)的的情況下,不做移動操作。這種情況,在中得到了優(yōu)化,通過四個(gè)指針,在每次循環(huán)中先處理特殊情況,并通過縮小指針范圍,獲得性能上的提升。 上篇文章已經(jīng)介紹過idff的處理邏輯主要分為三塊,處理textNode,element及component,但具體怎么處理component還沒有詳細(xì)介紹,接下來講一下preact是如何處理...

    Warren 評論0 收藏0
  • Luy 1.0 :一個(gè)React-like輪子的誕生

    摘要:司徒正美的一款了不起的化方案,支持到。行代碼內(nèi)實(shí)現(xiàn)一個(gè)胡子大哈實(shí)現(xiàn)的作品其實(shí)就是的了源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章源碼學(xué)習(xí)個(gè)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對比。 前言 在過去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開始寫了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱?,?..

    codecook 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<