我們為什麼需要React?

文章好看版本(markdown寫的,可能會好看一些)

一些廢話

或許在你看到這個話題的時候,腦海里一定會想

  • 組件化
  • 性能好
  • 大家都在用,好找工作

在現在MVVM框架橫行的年代裡,大家學習前端基本上都是直奔三大框架而去,這是沒錯的,畢竟熟練框架的操作,確實能很快的找到一份像樣的工作,但是很可能就不會知道「為什麼會出現這些框架」了,這意味著,在使用框架之後的很長一段時間裡,你都是知其然不知所以然。

這個帶來的問題就是,每當一個框架「蹦」出來的時候,你總會非常非常的緊張,覺得「他媽的又有要學的東西了,蛋疼」。之前的 React 協議的問題,著實把很多還沒入行的朋友嚇的直哆嗦,很多粉絲也都在問我,React是否還有學習的必要...

一)無框架的年代,我們如何開發的?

我們需要開發這樣的一個功能,點擊+號按鈕,上面的數字就加1。HTML代碼如下:

<div class="wrap">n <div class="text">0</div>n <button class=plus>n +n </button>n</div>n

再來一段Javascript的代碼,我們的功能就能完備了:

<script>n//document.querySelector,其實就是DOM的api,選擇一個元素nn const wrap = document.querySelector(.wrap)n const plus = wrap.querySelector(.plus)n const text = wrap.querySelector(.text)nn let number = 0n//dom api,給元素添加一個監聽器n plus.addEventListener(click, function () {n number++n text.innerHTML = numbern }, false)nn</script>n

上古時期的開發們大約就是這樣去書寫代碼,因為功能非常的簡單,所以代碼量也很少,思路很清晰。隨著頁面的元素越來越多,你這個「點擊加1按鈕」的功能可能被複用,那麼你現在用一個最快的辦法對其進行復用:複製HTML->複製JS代碼->粘貼HTML和JS代碼

這種方法是很挫的.....於是為了簡單化我們的流程,我們想出了一個聰明的辦法去復用

1.1看似聰明的辦法

(前提:因為FB總部被炸了,正在修理,導致ES6出來了,React還沒發布)

<script>n class Add {n render() {n return ` n <div class="text">0</div>n <button class=plus>n +n </button>`n }n }n const wrap = document.querySelector(.wrap)nn const add = new Add()n wrap.innerHTML = add.render()nn const plus = wrap.querySelector(.plus)n const text = wrap.querySelector(.text)nn let number = 0n plus.addEventListener(click, function () {n number++n text.innerHTML = numbern }, false)n</script>n

代碼稍微的一改,我們用一個class Add代替了HTML,當調用這個類的render函數的時候,就會返回一段字元串,這段字元串就是我們想要的組件。

由此,我們的組件復用方法變成了:複製JS代碼->粘貼JS代碼

這麼一來啊,我們就簡化了我們組件復用的流程。但是我們可以看到,我們手動操作DOM的次數太多了,而且邏輯非常的重複,因此,我們把這部分邏輯抽象一下,使得我們組件復用更加簡單。

1.2 更加簡單的組件復用

<script>n class Add {n createWrapper(string) {n //document.createElement, DOM api,根據標籤名字創建DOM元素n const wrapper = document.createElement(div)n wrapper.innerHTML = stringn return wrappern }nn render() {n const domString = ` n <div class="text">0</div>n <button class=plus>n +n </button>`nn //生成DOM元素n const wrapper = this.createWrapper(domString)n //DOM中查找元素n const plus = wrapper.querySelector(.plus)n const text = wrapper.querySelector(.text)nn let number = 0n plus.addEventListener(click, function () {n number++n text.innerHTML = numbern }, false)n return wrappern }n }nn const wrap = document.querySelector(.wrap)n const add = new Add()n wrap.appendChild(add.render())nn</script>n

現在我們的這個組件復用就非常的簡單了,我們只需要將class Add 通過export的方法導出以後,你可以在任何地方使用幾行代碼,就可以使用他,我們復用我們的組件就變成了:複製三行JS代碼->粘貼三行JS代碼

const wrap = document.querySelector(.wrap)nconst add = new Add()nwrap.appendChild(add.render())n

二)數據驅動組件

什麼是數據驅動呢?簡單的來說,就是「數據是什麼,我們就展示什麼」。回顧我們之前的組件內部

//生成DOM元素n const wrapper = this.createWrapper(domString)n //DOM中查找元素n const plus = wrapper.querySelector(.plus)n const text = wrapper.querySelector(.text)nn let number = 0n plus.addEventListener(click, function () {n number++n text.innerHTML = numbern }, false)n

我們在其中大量的使用了,querySelector這種api,使得我們需要各種手動的去操作DOM元素,如果我們的組件變成

<div class="wrap">n <div class="text">0</div>n <div class="text_2">0</div>n <div class="text_3">0</div>n <button class=plus>n +n </button>n</div>n

然後我需要點一下按鈕,三個標籤都得變化,那我們的代碼很可能就需要對每一個text進行選擇:

//生成DOM元素n const wrapper = this.createWrapper(domString)n //DOM中查找元素n const plus = wrapper.querySelector(.plus)n const text = wrapper.querySelector(.text)n const text_2 = wrapper.querySelector(.text_2)n const text_3 = wrapper.querySelector(.text_3)nn let number = 0n plus.addEventListener(click, function () {n number++n text.innerHTML = numbern text_2.innerHTML = numbern text_3.innerHTML = numbern }, false)n

這種做法,顯然是不符合我們的預期的,因為這非常影響我們的開發效率,而我們也僅僅是簡單的增添了一些新的標籤以及展示,我們都非得增加一大堆的代碼。

2.1 引入組件的狀態

「數據是什麼,我們就展示什麼」,這是我們最終的想要達到的成果,方法很多,但是最省代碼的方式和最簡單的方式就是:當我們數據發生改變的同時,我們根據數據的變化,重新渲染整個組件,那就簡單多了。

話非常的繞,我們的代碼這麼去寫:

<script>n class Add {n constructor() {//構造函數,設置staten this.state = {n number: 0n }n }n //重寫setState方法,使得每次調用setState,就會重新渲染,更新組件n setState(NewState) {n const oldElement = this.wrappern this.state = {...NewState}//ES6語法,展開操作符n this.wrapper = this.render()//重新渲染nn //拿到新的組件,更新原來的組件n if (this.update) this.update(oldElement, this.wrappern }n createWrapper(string) {n const wrapper = document.createElement(div)n wrapper.innerHTML = stringn return wrappern }n onClick() {//事件調用n let NewState = this.state.number + 1n this.setState({n number: NewStaten })n }n render() {n const domString = ` n <div class="text">${this.state.number}</div>n <div class="text_2">${this.state.number}</div>n <div class="text_3">${this.state.number}</div>n <button class=plus>n +n </button>`nn this.wrapper = this.createWrapper(domString)n const plus = this.wrapper.querySelector(.plus)nn plus.addEventListener(click, this.onClick.bind(this), false)n return this.wrappern }n }nn const wrap = document.querySelector(.wrap)n const add = new Add()n wrap.appendChild(add.render())n add.update = (old, next) => {//給組件定義一個方法n wrap.insertBefore(next, old)//插入新的組件n wrap.removeChild(old)//去掉舊的組件n }nn</script>n

代碼稍微有些長了,但是其實很好理解。我們最終達到的目的就是「當我們數據發生改變的同時,我們根據數據的變化,重新渲染整個組件」

2.2 抽象:讓所有組件都擁有自動更新的能力

我們現在已經自己開發出一個非常容易去復用的組件了,但是當我們要開發另外一個組件的時候或許就要重新寫一套這樣的「復用」邏輯。在我們的思想里,所有的組件都應該擁有「自動更新的能力」,因此我們將組件根據狀態,自動更新的能力抽取出來,那我們以後就非常方便了。

class ButtonComponent {n constructor() {n }n createWrapper(string) {n //document.createElement, DOM api,根據標籤名字創建DOM元素n const wrapper = document.createElement(div)n wrapper.innerHTML = stringn return wrappern }nn setState(newState) {n const oldElement = this.wrappern this.state = {...newState}n this.wrapper = this.renderElement()//渲染出元素n if (this.update) this.update(oldElement, this.wrapper)n }nn renderElement() {n this.wrapper = this.createWrapper(this.render())n const plus = this.wrapper.querySelector(.plus)n if (this.onClick) {n plus.addEventListener(click, this.onClick.bind(this), false)n }n return this.wrappern }nn render() { }//玩家需要重寫的函數n }n

好了,邏輯抽象完成。還記得我們的React有一個ReactDOM.render方法嗎?我們改一下名字直接叫做:

const renderToDOM = (component, DOMElement) => {n DOMElement.appendChild(component.renderElement())n component.update = (old, next) => {//給組件定義一個方法n DOMElement.insertBefore(next, old)//插入新的組件n DOMElement.removeChild(old)//去掉舊的組件n }n }n

最後呢,我們剛剛的組件,可以寫成這樣~

class Add extends ButtonComponent {n constructor() {n super()n this.state = {n number:0n }n }n onClick() {//事件調用n let NewState = this.state.number + 1n this.setState({n number: NewStaten })n }n render() {n return ` n <div class="text">${this.state.number}</div>n <div class="text_2">${this.state.number}</div>n <div class="text_3">${this.state.number}</div>n <button class=plus>n +n </button>`n }n }nn renderToDOM(new Add, document.getElementById(wrap))n

自此,我們的組件化就完成了。你可以看到,我們的組件其實外貌上已經非常像React,並且已經擁有了一個良好的組件復用能力。

最後:我們為什麼需要React?

通過上述的一點一點分析,實現我們可以得知,實際上React的出現,已經完完全全的顛覆了傳統的開發模式。

我們無須再去:

  • 使用DOM API,一個一個的元素進行更新,操作
  • 組件復用非常的簡單,幾乎每次復用之前寫的組件,就是1-2行代碼就搞定
  • 組件內部狀態,自己管理自己,跟外界完全無關

實際上,所有的框架出現都是為了解決「開發者困難」的問題。試想一下,一個10萬行的項目,整天都是一個一個的元素進行更新,操作,那請10個程序員都維護不過來。有了這些框架的幫助,我們就能夠更好的開發。

當我們站在這個角度去看前端框架的時候,我們就會發現,其實三大框架也不過就是:react牌鎚子,vue牌鎚子,ng牌鎚子,他們都是鎚子,以後就算有框架出來,也還是鎚子....

本文最終代碼:

215566435/A-ButtomComponent


推薦閱讀:

學習編程的好方法——控制台遊戲
git:一個本地分支可以對應多個遠程分支么?
High Flexibility Remote Development
為什麼說「函數式語言是沒有調用棧」的,所謂「函數式語言的思維」又是指什麼呢?

TAG:React | 前端开发 | 编程 |