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

資訊專欄INFORMATION COLUMN

40 行代碼內(nèi)實(shí)現(xiàn)一個(gè) React.js

twohappy / 1440人閱讀

摘要:代碼托管這個(gè)倉(cāng)庫(kù)。假設(shè)現(xiàn)在我們需要實(shí)現(xiàn)一個(gè)點(diǎn)贊取消點(diǎn)贊的功能。如果你對(duì)前端稍微有一點(diǎn)了解,你就順手拈來(lái)點(diǎn)贊為了現(xiàn)實(shí)當(dāng)中的實(shí)際情況,所以這里特易把這個(gè)的結(jié)構(gòu)搞得稍微復(fù)雜一些。這里非常暴力地使用了,把兩個(gè)按鈕粗魯?shù)夭迦肓水?dāng)中。

作者:胡子大哈
原文鏈接:http://huziketang.com/blog/posts/detail?postId=58aea515204d50674934c3ac

轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接和作者信息。

目錄

1 前言

2 一切從點(diǎn)贊說(shuō)起

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

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

3.2 生成 DOM 元素并且添加事件

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

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

4.2 重新插入新的 DOM 元素

5 抽象出 Component 類(lèi)

6 總結(jié)

1 前言

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

本文的目的是:揭開(kāi)對(duì)初學(xué)者看起來(lái)很很難理解的 React.js 的組件化形式的外衣,讓你有更多的精力和注意力去學(xué)習(xí) React.js 精髓的地方。如果你剛開(kāi)始學(xué)習(xí) React.js 并且感覺(jué)很迷茫,那么看完這篇文章以后就能夠解除一些疑惑。

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

2 一切從點(diǎn)贊說(shuō)起

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

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

[image:B4B41FF2-519A-4A7C-8035-0D5CD4EE8FFA-86900-00013723B2CAE361/8D274601-162D-4B36-B1E0-9C65FB0C494F.png]

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

HTML:

  
    

為了現(xiàn)實(shí)當(dāng)中的實(shí)際情況,所以這里特易把這個(gè) button 的 HTML 結(jié)構(gòu)搞得稍微復(fù)雜一些。有了這個(gè) HTML 結(jié)構(gòu),現(xiàn)在就給它加入一些 JavaScript 的行為:

JavaScript:

  const button = document.querySelector(".like-btn")
  const buttonText = button.querySelector(".like-text")
  let isLiked = false
  button.addEventListener("click", function () {
    isLiked = !isLiked
    if (isLiked) {
      buttonText.innerHTML = "取消"
    } else {
      buttonText.innerHTML = "點(diǎn)贊"
    }
  }, false)

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

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

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

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

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

  class LikeButton {
    render () {
      return `
        
      `
    }
  }

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

  const wrapper = document.querySelector(".wrapper")
  const likeButton1 = new LikeButton()
  wrapper.innerHTML = likeButton1.render()
  
  const likeButton2 = new LikeButton()
  wrapper.innerHTML += likeButton2.render()

[image:4AEFC6B6-F913-440E-9306-CCC454A7A30C-87312-00013B98FB6F8354/4555573C-8435-4079-9D64-C76913AB6E40.png]

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

3.2 生成 DOM 元素并且添加事件

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

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

// ::String => ::Document
const createDOMFromString = (domString) => {
  // TODO 
}

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

  class LikeButton {
    render () {
      this.el = createDOMFromString(`
        
      `)
      this.el.addEventListener("click", () => console.log("click"), false)
      return this.el
    }
  }

現(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)去。

    const wrapper = document.querySelector(".wrapper")

  const likeButton1 = new LikeButton()
  wrapper.appendChild(likeButton1.render())

  const likeButton2 = new LikeButton()
  wrapper.appendChild(likeButton2.render())

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

  class LikeButton {
    constructor () {
      this.state = { isLiked: false }
    }

    changeLikeText () {
      const likeText = this.el.querySelector(".like-text")
      this.state.isLiked = !this.state.isLiked
      if (this.state.isLiked) {
        likeText.innerHTML = "取消"
      } else {
        likeText.innerHTML = "點(diǎn)贊"
      }
    }

    render () {
      this.el = createDOMFromString(`
        
      `)
      this.el.addEventListener("click", this.changeLikeText.bind(this), false)
      return this.el
    }
  }

這里的代碼稍微長(zhǎng)了一些,但是還是很好理解。只不過(guò)是在給 LikeButton 類(lèi)添加了構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)會(huì)給每一個(gè) LikeButton 的實(shí)例添加一個(gè)對(duì)象 state,state 里面保存了每個(gè)按鈕自己是否點(diǎn)贊的狀態(tài)。還改寫(xiě)了原來(lái)的事件綁定函數(shù):原來(lái)只打印 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)類(lèi)似了。但其實(shí)我們根本沒(méi)有講 React.js 的任何內(nèi)容,我們一心一意只想怎么做好“組件化”。

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

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

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

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

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

  class LikeButton {
    constructor () {
      this.state = { isLiked: false }
    }

    setState (state) {
      this.state = state
      this.el = this.render()
    }

    changeLikeText () {
      this.setState({
        isLiked: !this.state.isLiked
      })
    }

    render () {
      this.el = createDOMFromString(`
        
      `)
      this.el.addEventListener("click", this.changeLikeText.bind(this), false)
      return this.el
    }
  }

其實(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 元素。

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

4.2 重新插入新的 DOM 元素

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

重新修改一下 setState 方法:

...
    setState (state) {
        const oldEl = this.el
      this.state = state
      this.el = this.render()
        if (this.onStateChange) this.onStateChange(oldEl, this.el)
    }
...

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

const likeButton = new LikeButton()
wrapper.appendChild(likeButton.render()) // 第一次插入 DOM 元素
component.onStateChange = (oldEl, newEl) => {
  wrapper.insertBefore(newEl, oldEl) // 插入新的元素
  wrapper.removeChild(oldEl) // 刪除舊的元素
}

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

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

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

5 抽象出 Component 類(lèi)

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

  class Component {
    constructor (props = {}) {
      this.props = props
    }

    setState (state) {
      const oldEl = this.el
      this.state = state
      this.el = this.renderDOM()
      if (this.onStateChange) this.onStateChange(oldEl, this.el)
    }

    renderDOM () {
      this.el = createDOMFromString(this.render())
      if (this.onClick) {
        this.el.addEventListener("click", this.onClick.bind(this), false)
      }
      return this.el
    }
  }

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

  const mount = (wrapper, component) => {
    wrapper.appendChild(component.renderDOM())
    component.onStateChange = (oldEl, newEl) => {
      wrapper.insertBefore(newEl, oldEl)
      wrapper.removeChild(oldEl)
    }
  }

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

  class LikeButton extends Component {
    constructor (props) {
      super(props)
      this.state = { isLiked: false }
    }

    onClick () {
      this.setState({
        isLiked: !this.state.isLiked
      })
    }

    render () {
      return `
        
      `
    }
  }

  mount(wrapper, new LikeButton({ word: "hello" }))

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

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

  class RedBlueButton extends Component {
    constructor (props) {
      super(props)
      this.state = {
        color: "red"
      }
    }

    onClick () {
      this.setState({
        color: "blue"
      })
    }

    render () {
      return `
        
${this.state.color}
` } }

簡(jiǎn)單好用,完整的代碼可以在這里找到: 倉(cāng)庫(kù)

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

  const createDOMFromString = (domString) => {
    const div = document.createElement("div")
    div.innerHTML = domString
    return div
  }
6 總結(jié)

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

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

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

本文并沒(méi)有涉及到 Virtual DOM 的任何內(nèi)容,有需要的同學(xué)可以參考一下這篇博客 ,介紹的很詳盡。有興趣的同學(xué)可以把兩者結(jié)合起來(lái),把 Virtual DOM 替代本文暴力處理的 mount 中的實(shí)現(xiàn),真正實(shí)現(xiàn)一個(gè) React.js。

如果你對(duì)本文的內(nèi)容有疑惑,可以關(guān)注我的知乎專欄并且評(píng)論或者給我知乎發(fā)私信。

我最近正在寫(xiě)一本《React.js 小書(shū)》,對(duì) React.js 感興趣的童鞋,歡迎指點(diǎn)。

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

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

相關(guān)文章

  • 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è)人文章這幾片文章的作者都是司徒正美,全面的解析和官方的對(duì)比。 前言 在過(guò)去的一個(gè)多月中,為了能夠更深入的學(xué)習(xí),使用React,了解React內(nèi)部算法,數(shù)據(jù)結(jié)構(gòu),我自己,從零開(kāi)始寫(xiě)了一個(gè)玩具框架。 截止今日,終于可以發(fā)布第一個(gè)版本,因?yàn)榫驮谧蛱欤?..

    codecook 評(píng)論0 收藏0
  • React.js 小書(shū) Lesson4 - 前端組件化(三):抽象出公共組件類(lèi)

    摘要:最后抽離出來(lái)了一個(gè)類(lèi),可以幫助我們更好的做組件化。一個(gè)組件有自己的顯示形態(tài)上面的結(jié)構(gòu)和內(nèi)容行為,組件的顯示形態(tài)和行為可以由數(shù)據(jù)狀態(tài)和配置參數(shù)共同決定。接下來(lái)我們開(kāi)始正式進(jìn)入主題,開(kāi)始正式介紹。下一節(jié)鏈接直達(dá)小書(shū)基本環(huán)境安裝 React.js 小書(shū) Lesson4 - 前端組件化(三):抽象出公共組件類(lèi) 本文作者:胡子大哈本文原文:http://huziketang.com/books...

    jsbintask 評(píng)論0 收藏0
  • React.js組件化開(kāi)發(fā)第一步(框架搭建)

    摘要:開(kāi)始前安裝安裝安裝安裝完成后將鏡像替換成國(guó)內(nèi)的安裝查看安裝版本項(xiàng)目初始化命令行切到需要?jiǎng)?chuàng)建項(xiàng)目的目錄內(nèi)然后執(zhí)行是項(xiàng)目的名稱也是文件夾的名稱命令行切到剛創(chuàng)建的項(xiàng)目運(yùn)行項(xiàng)目執(zhí)行以下命令會(huì)自動(dòng)打開(kāi)瀏覽器并防問(wèn)初始化生成 開(kāi)始前 安裝 node.js; 安裝 cnpm; 安裝 yarn; 安裝完成yarn后, 將鏡像替換成國(guó)內(nèi)的: $ yarn config set registry h...

    Betta 評(píng)論0 收藏0
  • js技術(shù) - 收藏集 - 掘金

    摘要:還記得剛開(kāi)始學(xué)習(xí)的時(shí)候,內(nèi)存管理前端掘金作為一門(mén)高級(jí)語(yǔ)言,并不像低級(jí)語(yǔ)言那樣擁有對(duì)內(nèi)存的完全掌控。第三方庫(kù)的行代碼內(nèi)實(shí)現(xiàn)一個(gè)前端掘金前言本文會(huì)教你如何在行代碼內(nèi),不依賴任何第三方的庫(kù),用純實(shí)現(xiàn)一個(gè)。 (譯) 如何使用 JavaScript 構(gòu)建響應(yīng)式引擎 —— Part 1:可觀察的對(duì)象 - 掘金原文地址:How to build a reactive engine in JavaSc...

    Guakin_Huang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<