# 前端組件化(一):從一個簡單的例子講起
> 作者:[胡子大哈](http://huziketang.com/books/react) </br>
> 原文鏈接: [http://huziketang.com/books/react/lesson2](http://huziketang.com/books/react/lesson2)
**轉載請注明出處,保留原文鏈接以及作者信息**
很多課程一上來就給大家如何配置環境、怎么寫 React.js 組件。但是本課程還是希望大家對問題的根源有一個更加深入的了解,其實很多的庫、框架都是解決類似的問題。只有我們對這些庫、框架解決的問題有深入的了解和思考以后,我們才能得心應手地使用它們,并且有新的框架出來也不會太過迷茫;因為其實它們解決都是同一個問題。
這兩節課我們來探討一下是什么樣的問題導致了我們需要前端頁面進行組件化,前端頁面的組件化需要解決什么樣的問題。后續課程我們再來看看 React.js 是怎么解決這些問題的。
所以這幾節所講的內容將和 React.js 的內容沒有太大的關系,但是如果你能順利了解這幾節的內容,那么后面哪些對新手來說很復雜的概念對你來說就是非常自然的事。
## 一個簡單的點贊功能
我們會從一個簡單的點贊功能講起。 假設現在我們需要實現一個點贊、取消點贊的功能。
[](http://react.huziketang.com/assets/img/posts/B7575C67-64F8-4A13-9C63-4D6805FA360D.png)
如果你對前端稍微有一點了解,你就順手拈來:
HTML:
~~~
<body>
<div class='wrapper'>
<button class='like-btn'>
<span class='like-text'>點贊</span>
<span></span>
</button>
</div>
</body>
~~~
為了模擬現實當中的實際情況,所以這里特意把這個?`button`?里面的 HTML 結構搞得稍微復雜一些。有了這個 HTML 結構,現在就給它加入一些 JavaScript 的行為:
JavaScript:
~~~
const button = document.querySelector('.like-btn')
const buttonText = button.querySelector('.like-text')
let isLiked = false
button.addEventListener('click', () => {
isLiked = !isLiked
if (isLiked) {
buttonText.innerHTML = '取消'
} else {
buttonText.innerHTML = '點贊'
}
}, false)
~~~
功能和實現都很簡單,按鈕已經可以提供點贊和取消點贊的功能。這時候你的同事跑過來了,說他很喜歡你的按鈕,他也想用你寫的這個點贊功能。這時候問題就來了,你就會發現這種實現方式很致命:你的同事要把整個?`button`?和里面的結構復制過去,還有整段 JavaScript 代碼也要復制過去。這樣的實現方式沒有任何可復用性。
## 結構復用
現在我們來重新編寫這個點贊功能,讓它具備一定的可復用。這次我們先寫一個類,這個類有 render 方法,這個方法里面直接返回一個表示 HTML 結構的字符串:
~~~
class LikeButton {
render () {
return `
<button id='like-btn'>
<span class='like-text'>贊</span>
<span></span>
</button>
`
}
}
~~~
然后可以用這個類來構建不同的點贊功能的實例,然后把它們插到頁面中。
~~~
const wrapper = document.querySelector('.wrapper')
const likeButton1 = new LikeButton()
wrapper.innerHTML = likeButton1.render()
const likeButton2 = new LikeButton()
wrapper.innerHTML += likeButton2.render()
~~~
[](http://react.huziketang.com/assets/img/posts/B7575C67-64F8-4A13-9C63-4D6805FA360D.png)
這里非常暴力地使用了 innerHTML ,把兩個按鈕粗魯地插入了 wrapper 當中。雖然你可能會對這種實現方式非常不滿意,但我們還是勉強了實現了結構的復用。我們后面再來優化它。
## 實現簡單的組件化
你一定會發現,現在的按鈕是死的,你點擊它它根本不會有什么反應。因為根本沒有往上面添加事件。但是問題來了,`LikeButton`?類里面是雖然說有一個?`button`,但是這玩意根本就是在字符串里面的。你怎么能往一個字符串里面添加事件呢?DOM 事件的 API 只有 DOM 結構才能用。
我們需要 DOM 結構,準確地來說:我們需要這個點贊功能的 HTML 字符串表示的 DOM 結構。假設我們現在有一個函數?`createDOMFromString`?,你往這個函數傳入 HTML 字符串,但是它會把相應的 DOM 元素返回給你。這個問題就可以額解決了。
~~~
// ::String => ::Document
const createDOMFromString = (domString) => {
// TODO
}
~~~
先不用管這個函數應該怎么實現,先知道它是干嘛的。拿來用就好,這時候用它來改寫一下?`LikeButton`?類:
~~~
class LikeButton {
render () {
this.el = createDOMFromString(`
<button class='like-button'>
<span class='like-text'>點贊</span>
<span></span>
</button>
`)
this.el.addEventListener('click', () => console.log('click'), false)
return this.el
}
}
~~~
現在?`render()`?返回的不是一個 html 字符串了,而是一個由這個 html 字符串所生成的 DOM。在返回 DOM 元素之前會先給這個 DOM 元素上添加事件再返回。
因為現在?`render`?返回的是 DOM 元素,所以不能用?`innerHTML`?暴力地插入 wrapper。而是要用 DOM API 插進去。
~~~
const wrapper = document.querySelector('.wrapper')
const likeButton1 = new LikeButton()
wrapper.appendChild(likeButton1.render())
const likeButton2 = new LikeButton()
wrapper.appendChild(likeButton2.render())
~~~
現在你點擊這兩個按鈕,每個按鈕都會在控制臺打印?`click`,說明事件綁定成功了。但是按鈕上的文本還是沒有發生改變,只要稍微改動一下?`LikeButton`?的代碼就可以完成完整的功能:
~~~
class LikeButton {
constructor () {
this.state = { isLiked: false }
}
changeLikeText () {
const likeText = this.el.querySelector('.like-text')
this.state.isLiked = !this.state.isLiked
likeText.innerHTML = this.state.isLiked ? '取消' : '點贊'
}
render () {
this.el = createDOMFromString(`
<button class='like-button'>
<span class='like-text'>點贊</span>
<span></span>
</button>
`)
this.el.addEventListener('click', this.changeLikeText.bind(this), false)
return this.el
}
}
~~~
這里的代碼稍微長了一些,但是還是很好理解。只不過是在給?`LikeButton`?類添加了構造函數,這個構造函數會給每一個?`LikeButton`?的實例添加一個對象?`state`,`state`?里面保存了每個按鈕自己是否點贊的狀態。還改寫了原來的事件綁定函數:原來只打印?`click`,現在點擊的按鈕的時候會調用?`changeLikeText`?方法,這個方法會根據?`this.state`?的狀態改變點贊按鈕的文本。
現在這個組件的可復用性已經很不錯了,你的同事們只要實例化一下然后插入到 DOM 里面去就好了。
下一節我們繼續優化這個例子,讓它更加通用。
- 前言
- 第一階段
- Lesson 1 - React.js 簡介
- Lesson 2 - 前端組件化(一):從一個簡單的例子講起
- Lesson 3 - 前端組件化(二):優化 DOM 操作
- Lesson 4 - 前端組件化(三):抽象出公共組件類
- Lesson 5 - React.js 基本環境安裝
- Lesson 6 - 使用 JSX 描述 UI 信息
- Lesson 7 - 組件的 render 方法
- Lesson 8 - 組件的組合、嵌套和組件樹
- Lesson 9 - 事件監聽
- Lesson 10 - 組件的 state 和 setState
- Lesson 11 - 配置組件的 props
- Lesson 12 - state vs props
- Lesson 13 - 渲染列表數據
- Lesson 14 - 實戰分析:評論功能(一)
- Lesson 15 - 實戰分析:評論功能(二)
- Lesson 16 - 實戰分析:評論功能(三)
- 第二階段
- Lesson 17 - 前端應用狀態管理 —— 狀態提升
- Lesson 18 - 掛載階段的組件生命周期(一)
- Lesson 19 - 掛載階段的組件生命周期(二)
- Lesson 20 - 更新階段的組件生命周期
- Lesson 21 - ref 和 React.js 中的 DOM 操作
- Lesson 22 - props.children 和容器類組件
- Lesson 23 - dangerouslySetHTML 和 style 屬性
- Lesson 24 - PropTypes 和組件參數驗證
- Lesson 25 - 實戰分析:評論功能(四)
- Lesson 26 - 實戰分析:評論功能(五)
- Lesson 27 - 實戰分析:評論功能(六)