日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
40行代碼內(nèi)實(shí)現(xiàn)一個(gè)React.js

一、前言

本文會(huì)教你如何在 40 行代碼內(nèi),不依賴任何第三方的庫(kù),用純 JavaScript 實(shí)現(xiàn)一個(gè) React.js 。

本文的目的是:揭開對(duì)初學(xué)者看起來很很難理解的 React.js 的組件化形式的外衣。如果你剛開始學(xué)習(xí) React.js 并且感覺很迷茫,那么看完這篇文章以后就能夠解除一些疑惑。

另外注意,本文所實(shí)現(xiàn)的代碼只用于說明教學(xué)展示,并不適用于生產(chǎn)環(huán)境。代碼托管這個(gè) 倉(cāng)庫(kù)。心急如焚的同學(xué)可以先去看代碼,但本文會(huì)從最基礎(chǔ)的內(nèi)容開始解釋。

二、一切從點(diǎn)贊說起

接下來所有的代碼都會(huì)從一個(gè)基本的點(diǎn)贊功能開始演化,你會(huì)逐漸看到,文章代碼慢慢地越來越像 React.js 的組件代碼。而在這個(gè)過程里面,大家需要只需要跟著文章的思路,就可以在代碼的演化當(dāng)中體會(huì)到組件化形式。

假設(shè)現(xiàn)在我們需要實(shí)現(xiàn)一個(gè)點(diǎn)贊、取消點(diǎn)贊的功能。

如果你對(duì)前端稍微有一點(diǎn)了解,你就順手拈來:

HTML:

 
 
 
 
  1.  
  2.      
  3.        
  4.         點(diǎn)贊 
  5.          
  6.        
  7.     
 
  •    
  • 為了現(xiàn)實(shí)當(dāng)中的實(shí)際情況,所以這里特意把這個(gè) button 的 HTML 結(jié)構(gòu)搞得稍微復(fù)雜一些。有了這個(gè) HTML 結(jié)構(gòu),現(xiàn)在就給它加入一些 JavaScript 的行為:

    JavaScript:

     
     
     
     
    1. const button = document.querySelector('.like-btn') 
    2.   const buttonbuttonText = button.querySelector('.like-text') 
    3.   let isLiked = false 
    4.   button.addEventListener('click', function () { 
    5.     isLiked = !isLiked 
    6.     if (isLiked) { 
    7.       buttonText.innerHTML = '取消' 
    8.     } else { 
    9.       buttonText.innerHTML = '點(diǎn)贊' 
    10.     } 
    11.   }, false) 

    功能和實(shí)現(xiàn)都很簡(jiǎn)單,按鈕已經(jīng)可以提供點(diǎn)贊和取消點(diǎn)贊的功能。這時(shí)候你的同事跑過來了,說他很喜歡你的按鈕,他也想用你寫的這個(gè)點(diǎn)贊功能。你就會(huì)發(fā)現(xiàn)這種實(shí)現(xiàn)方式很致命:你的同事要把整個(gè) button 和里面的結(jié)構(gòu)復(fù)制過去,還有整段 JavaScript 代碼也要復(fù)制過去。這樣的實(shí)現(xiàn)方式?jīng)]有任何可復(fù)用性。

    三、實(shí)現(xiàn)可復(fù)用性

    所以現(xiàn)在我們來想辦法解決這個(gè)問題,讓這個(gè)點(diǎn)贊功能具有較好的可復(fù)用的效果,那么你的同事們就可以輕松自在地使用這個(gè)點(diǎn)贊功能。

    1. 結(jié)構(gòu)復(fù)用

    現(xiàn)在我們來重新編寫這個(gè)點(diǎn)贊功能。這次我們先寫一個(gè)類,這個(gè)類有 render 方法,這個(gè)方法里面直接返回一個(gè)表示 HTML 結(jié)構(gòu)的字符串:

     
     
     
     
    1. class LikeButton { 
    2.    render () { 
    3.      return ` 
    4.         
    5.          贊 
    6.           
    7.         
    8.      ` 
    9.    } 
    10.  } 

    然后可以用這個(gè)類來構(gòu)建不同的點(diǎn)贊功能的實(shí)例,然后把它們插到頁面中。

     
     
     
     
    1. const wrapper = document.querySelector('.wrapper') 
    2.   const likeButton1 = new LikeButton() 
    3.   wrapper.innerHTML = likeButton1.render() 
    4.  
    5.   const likeButton2 = new LikeButton() 
    6.   wrapper.innerHTML += likeButton2.render() 

    這里非常暴力地使用了 innerHTML ,把兩個(gè)按鈕粗魯?shù)夭迦肓?wrapper 當(dāng)中。雖然你可能會(huì)對(duì)這種實(shí)現(xiàn)方式非常不滿意,但我們還是勉強(qiáng)了實(shí)現(xiàn)了結(jié)構(gòu)的復(fù)用。我們后面再來優(yōu)化它。

    2. 生成 DOM 元素并且添加事件

    你一定會(huì)發(fā)現(xiàn),現(xiàn)在的按鈕是死的,你點(diǎn)擊它它根本不會(huì)有什么反應(yīng)。因?yàn)楦緵]有往上面添加事件。但是問題來了,LikeButton 類里面是雖然說有一個(gè) button,但是這玩意根本就是在字符串里面的。你怎么能往一個(gè)字符串里面添加事件呢?DOM 事件的 API 只有 DOM 結(jié)構(gòu)才能用。

    我們需要 DOM 結(jié)構(gòu),準(zhǔn)確地來說:我們需要這個(gè)點(diǎn)贊功能的 HTML 字符串代表的 DOM 結(jié)構(gòu)。假設(shè)我們現(xiàn)在有一個(gè)函數(shù) createDOMFromString ,你往這個(gè)函數(shù)傳入 HTML 字符串,但是它會(huì)把相應(yīng)的 DOM 元素返回給你。這個(gè)問題就可以額解決了。

     
     
     
     
    1. // ::String => ::Document 
    2. const createDOMFromString = (domString) => { 
    3.   // TODO  

    先不用管這個(gè)函數(shù)應(yīng)該怎么實(shí)現(xiàn),先知道它是干嘛的。拿來用就好,這時(shí)候用它來改寫一下 LikeButton 類:

     
     
     
     
    1. class LikeButton { 
    2.     render () { 
    3.       this.el = createDOMFromString(` 
    4.          
    5.           點(diǎn)贊 
    6.            
    7.          
    8.       `) 
    9.       this.el.addEventListener('click', () => console.log('click'), false) 
    10.       return this.el 
    11.     } 
    12.   } 

    現(xiàn)在 render() 返回的不是一個(gè) html 字符串了,而是一個(gè)由這個(gè) html 字符串所生成的 DOM。在返回 DOM 元素之前會(huì)先給這個(gè) DOM 元素上添加事件再返回。

    因?yàn)楝F(xiàn)在 render 返回的是 DOM 元素,所以不能用 innerHTML 暴力地插入 wrapper。而是要用 DOM API 插進(jìn)去。

     
     
     
     
    1. const wrapper = document.querySelector('.wrapper') 
    2.  
    3. const likeButton1 = new LikeButton() 
    4. wrapper.appendChild(likeButton1.render()) 
    5.  
    6. const likeButton2 = new LikeButton() 
    7. wrapper.appendChild(likeButton2.render()) 

    現(xiàn)在你點(diǎn)擊這兩個(gè)按鈕,每個(gè)按鈕都會(huì)在控制臺(tái)打印 click,說明事件綁定成功了。但是按鈕上的文本還是沒有發(fā)生改變,只要稍微改動(dòng)一下 LikeButton 的代碼就可以完成完整的功能:

     
     
     
     
    1. class LikeButton { 
    2.     constructor () { 
    3.       this.state = { isLiked: false } 
    4.     } 
    5.  
    6.     changeLikeText () { 
    7.       const likeText = this.el.querySelector('.like-text') 
    8.       this.state.isLiked = !this.state.isLiked 
    9.       if (this.state.isLiked) { 
    10.         likeText.innerHTML = '取消' 
    11.       } else { 
    12.         likeText.innerHTML = '點(diǎn)贊' 
    13.       } 
    14.     } 
    15.  
    16.     render () { 
    17.       this.el = createDOMFromString(` 
    18.          
    19.           點(diǎn)贊 
    20.            
    21.          
    22.       `) 
    23.       this.el.addEventListener('click', this.changeLikeText.bind(this), false) 
    24.       return this.el 
    25.     } 
    26.   } 

    這里的代碼稍微長(zhǎng)了一些,但是還是很好理解。只不過是在給 LikeButton 類添加了構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)會(huì)給每一個(gè) LikeButton 的實(shí)例添加一個(gè)對(duì)象 state,state 里面保存了每個(gè)按鈕自己是否點(diǎn)贊的狀態(tài)。還改寫了原來的事件綁定函數(shù):原來只打印 click,現(xiàn)在點(diǎn)擊的按鈕的時(shí)候會(huì)調(diào)用 changeLikeText 方法,這個(gè)方法會(huì)根據(jù) this.state 的狀態(tài)改變點(diǎn)贊按鈕的文本。

    如果你現(xiàn)在還能跟得上文章的思路,那么你留意下,現(xiàn)在的代碼已經(jīng)和 React.js 的組件代碼有點(diǎn)類似了。但其實(shí)我們根本沒有講 React.js 的任何內(nèi)容,我們一心一意只想怎么做好“組件化”。

    現(xiàn)在這個(gè)組件的可復(fù)用性已經(jīng)很不錯(cuò)了,你的同事們只要實(shí)例化一下然后插入到 DOM 里面去就好了。

    四、為什么不暴力一點(diǎn)?

    仔細(xì)留意一下 changeLikeText 函數(shù),這個(gè)函數(shù)包含了 DOM 操作,現(xiàn)在看起來比較簡(jiǎn)單,那是因?yàn)楝F(xiàn)在只有 isLiked 一個(gè)狀態(tài)。但想一下,因?yàn)槟愕臄?shù)據(jù)狀態(tài)改變了你就需要去更新頁面的內(nèi)容,所以如果你的組件包含了很多狀態(tài),那么你的組件基本全部都是 DOM 操作。一個(gè)組件包含很多狀態(tài)的情況非常常見,所以這里還有優(yōu)化的空間:如何盡量減少這種手動(dòng) DOM 操作?

    1. 狀態(tài)改變 -> 構(gòu)建新的 DOM 元素

    這里要提出的一種解決方案:一旦狀態(tài)發(fā)生改變,就重新調(diào)用 render 方法,構(gòu)建一個(gè)新的 DOM 元素。這樣做的好處是什么呢?好處就是你可以在 render 方法里面使用***的 this.state 來構(gòu)造不同 HTML 結(jié)構(gòu)的字符串,并且通過這個(gè)字符串構(gòu)造不同的 DOM 元素。頁面就更新了!聽起來有點(diǎn)繞,看看代碼怎么寫:

     
     
     
     
    1. class LikeButton { 
    2.     constructor () { 
    3.       this.state = { isLiked: false } 
    4.     } 
    5.  
    6.     setState (state) { 
    7.       this.state = state 
    8.       thisthis.el = this.render() 
    9.     } 
    10.  
    11.     changeLikeText () { 
    12.       this.setState({ 
    13.         isLiked: !this.state.isLiked 
    14.       }) 
    15.     } 
    16.  
    17.     render () { 
    18.       this.el = createDOMFromString(` 
    19.          
    20.           ${this.state.isLiked ? '取消' : '點(diǎn)贊'} 
    21.            
    22.          
    23.       `) 
    24.       this.el.addEventListener('click', this.changeLikeText.bind(this), false) 
    25.       return this.el 
    26.     } 
    27.   } 

    其實(shí)只是改了幾個(gè)小地方:

    • render 函數(shù)里面的 HTML 字符串會(huì)根據(jù) this.state 不同而不同(這里是用了 ES6 的字符串特性,做這種事情很方便)。
    • 新增一個(gè) setState 函數(shù),這個(gè)函數(shù)接受一個(gè)對(duì)象作為參數(shù);它會(huì)設(shè)置實(shí)例的 state,然后重新調(diào)用一下 render 方法。
    • 當(dāng)用戶點(diǎn)擊按鈕的時(shí)候, changeLikeText 會(huì)構(gòu)建新的 state 對(duì)象,這個(gè)新的 state ,傳入 setState 函數(shù)當(dāng)中。

    這樣的結(jié)果就是,用戶每次點(diǎn)擊,changeLikeText 都會(huì)調(diào)用改變組件狀態(tài)然后調(diào)用 setState;setState 會(huì)調(diào)用 render 方法重新構(gòu)建新的 DOM 元素;render 方法會(huì)根據(jù) state 的不同構(gòu)建不同的 DOM 元素。

    也就是說,你只要調(diào)用 setState,組件就會(huì)重新渲染。我們順利地消除了沒必要的 DOM 操作。

    2. 重新插入新的 DOM 元素

    上面的改進(jìn)不會(huì)有什么效果,因?yàn)槟阕屑?xì)看一下就會(huì)發(fā)現(xiàn),其實(shí)重新渲染的 DOM 元素并沒有插入到頁面當(dāng)中。所以這個(gè)組件之外,你需要知道這個(gè)組件發(fā)生了改變,并且把新的 DOM 元素更新到頁面當(dāng)中。

    重新修改一下 setState 方法:

     
     
     
     
    1. ... 
    2.     setState (state) { 
    3.       const oldEl = this.el 
    4.       this.state = state 
    5.       thisthis.el = this.render() 
    6.       if (this.onStateChange) this.onStateChange(oldEl, this.el) 
    7.     } 
    8. ... 

    使用這個(gè)組件的時(shí)候:

     
     
     
     
    1. const likeButton = new LikeButton() 
    2. wrapper.appendChild(likeButton.render()) // ***次插入 DOM 元素 
    3. component.onStateChange = (oldEl, newEl) => { 
    4.   wrapper.insertBefore(newEl, oldEl) // 插入新的元素 
    5.   wrapper.removeChild(oldEl) // 刪除舊的元素 

    這里每次 setState 都會(huì)調(diào)用 onStateChange 方法,而這個(gè)方法是實(shí)例化以后時(shí)候被設(shè)置的,所以你可以自定義 onStateChange 的行為。這里做的事是,每當(dāng) setState 的時(shí)候,就會(huì)把插入新的 DOM 元素,然后刪除舊的元素,頁面就更新了。這里已經(jīng)做到了進(jìn)一步的優(yōu)化了:現(xiàn)在不需要再手動(dòng)更新頁面了。

    非一般的暴力。不過沒有關(guān)系,這種暴力行為可以被 Virtual-DOM 的 diff 策略規(guī)避掉,但這不是本文章所討論的范圍。

    這個(gè)版本的點(diǎn)贊功能很不錯(cuò),我可以繼續(xù)往上面加功能,而且還不需要手動(dòng)操作DOM。但是有一個(gè)不好的地方,如果我要重新另外做一個(gè)新組件,譬如說評(píng)論組件,那么里面的這些 setState 方法要重新寫一遍,其實(shí)這些東西都可以抽出來。

    五、抽象出 Component 類

    為了讓代碼更靈活,可以寫更多的組件,我把這種模式抽象出來,放到一個(gè) Component 類當(dāng)中:

     
     
     
     
    1. class Component { 
    2.     constructor (props = {}) { 
    3.       this.props = props 
    4.     } 
    5.  
    6.     setState (state) { 
    7.       const oldEl = this.el 
    8.       this.state = state 
    9.       thisthis.el = this.renderDOM() 
    10.       if (this.onStateChange) this.onStateChange(oldEl, this.el) 
    11.     } 
    12.  
    13.     renderDOM () { 
    14.       this.el = createDOMFromString(this.render()) 
    15.       if (this.onClick) { 
    16.         this.el.addEventListener('click', this.onClick.bind(this), false) 
    17.       } 
    18.       return this.el 
    19.     } 
    20.   } 

    還有一個(gè)額外的 mount 的方法,其實(shí)就是把組件的 DOM 元素插入頁面,并且在 setState 的時(shí)候更新頁面:

     
     
     
     
    1. const mount = (wrapper, component) => { 
    2.     wrapper.appendChild(component.renderDOM()) 
    3.     component.onStateChange = (oldEl, newEl) => { 
    4.       wrapper.insertBefore(newEl, oldEl) 
    5.       wrapper.removeChild(oldEl) 
    6.     } 
    7.   } 

    這樣的話我們重新寫點(diǎn)贊組件就會(huì)變成:

     
     
     
     
    1. class LikeButton extends Component { 
    2.     constructor (props) { 
    3.       super(props) 
    4.       this.state = { isLiked: false } 
    5.     } 
    6.  
    7.     onClick () { 
    8.       this.setState({ 
    9.         isLiked: !this.state.isLiked 
    10.       }) 
    11.     } 
    12.  
    13.     render () { 
    14.       return ` 
    15.          
    16.           ${this.props.word || ''} ${this.state.isLiked ? '取消' : '點(diǎn)贊'} 
    17.            
    18.          
    19.       ` 
    20.     } 
    21.   } 
    22.  
    23.   mount(wrapper, new LikeButton({ word: 'hello' })) 

    有沒有發(fā)現(xiàn)你寫的代碼已經(jīng)和 React.js 的組件寫法很相似了?而且還是可以正常運(yùn)作的代碼,而且我們從頭到尾都是用純的 JavaScript,沒有依賴任何第三方庫(kù)。(注意這里加入了上面沒有提到過點(diǎn) props,可以給組件傳入配置屬性,跟 React.js 一樣)。

    只要有了上面那個(gè) Component 類和 mount 方法加起來不足40行代碼就可以做到組件化。如果我們需要寫另外一個(gè)組件,只需要像上面那樣,簡(jiǎn)單地繼承一下 Component 類就好了:

     
     
     
     
    1. class RedBlueButton extends Component { 
    2.     constructor (props) { 
    3.       super(props) 
    4.       this.state = { 
    5.         color: 'red' 
    6.       } 
    7.     } 
    8.  
    9.     onClick () { 
    10.       this.setState({ 
    11.         color: 'blue' 
    12.       }) 
    13.     } 
    14.  
    15.     render () { 
    16.       return ` 
    17.         ${this.state.color}
     
  •       ` 
  •     } 
  •   } 
  • 簡(jiǎn)單好用,完整的代碼可以在這里找到: React.js in 40

    噢,忘了,還有一個(gè)神秘的 createDOMFromString,其實(shí)它更簡(jiǎn)單:

     
     
     
     
    1. const createDOMFromString = (domString) => { 
    2.     const div = document.createElement('div') 
    3.     div.innerHTML = domString 
    4.     return div 
    5.   } 

    六、總結(jié)

    你到底能從文章中獲取到什么?

    好吧,我承認(rèn)我標(biāo)題黨了,這個(gè) 40 行不到的代碼其實(shí)是一個(gè)殘廢而且智障版的 React.js,沒有 JSX ,沒有組件嵌套等等。它只是 React.js 組件化表現(xiàn)形式的一種實(shí)現(xiàn)而已。

    React 的 setState 、props 等等都只不過是一種形式,而很多初學(xué)者會(huì)被它這種形式作迷惑。本篇文章其實(shí)就是揭露了這種組件化形式的實(shí)現(xiàn)原理。如果你正在學(xué)習(xí)或者學(xué)習(xí) React.js 過程很迷茫,那么看完這篇文章以后就能夠解除一些疑惑。

    點(diǎn)擊《40 行代碼內(nèi)實(shí)現(xiàn)一個(gè) React.js》閱讀原文。

    【本文是專欄作者“胡子大哈”的原創(chuàng)文章,轉(zhuǎn)載請(qǐng)聯(lián)系作者本人獲取授權(quán)】

    戳這里,看該作者更多好文


    網(wǎng)頁標(biāo)題:40行代碼內(nèi)實(shí)現(xiàn)一個(gè)React.js
    鏈接分享:http://www.dlmjj.cn/article/cdhdhig.html