本文做者:鬍子大哈
本文原文:http://huziketang.com/books/react/lesson2javascript
轉載請註明出處,保留原文連接以及做者信息html
在線閱讀:http://huziketang.com/books/react/前端
React.js 是一個幫助你構建頁面 UI 的庫。若是你熟悉 MVC 概念的話,那麼 React 的組件就至關於 MVC 裏面的 View。若是你不熟悉也不要緊,你能夠簡單地理解爲,React.js 將幫助咱們將界面分紅了各個獨立的小塊,每個塊就是組件,這些組件之間能夠組合、嵌套,就成了咱們的頁面。java
一個組件的顯示形態和行爲有多是由某些數據決定的。而數據是可能發生改變的,這時候組件的顯示形態就會發生相應的改變。而 React.js 也提供了一種很是高效的方式幫助咱們作到了數據和組件顯示形態之間的同步。react
React.js 不是一個框架,它只是一個庫。它只提供 UI (view)層面的解決方案。在實際的項目當中,它並不能解決咱們全部的問題,須要結合其它的庫,例如 Redux、React-router 等來協助提供完整的解決方法。app
不少課程一上來就給你們如何配置環境、怎麼寫 React.js 組件。可是本課程仍是但願你們對問題的根源有一個更加深刻的瞭解,其實不少的庫、框架都是解決相似的問題。只有咱們對這些庫、框架解決的問題有深刻的瞭解和思考之後,咱們才能駕輕就熟地使用它們,而且有新的框架出來也不會太過迷茫;由於其實它們解決都是同一個問題。框架
這兩節課咱們來探討一下是什麼樣的問題致使了咱們須要前端頁面進行組件化,前端頁面的組件化須要解決什麼樣的問題。後續課程咱們再來看看 React.js 是怎麼解決這些問題的。less
因此這幾節所講的內容將和 React.js 的內容沒有太大的關係,可是若是你能順利瞭解這幾節的內容,那麼後面哪些對新手來講很複雜的概念對你來講就是很是天然的事。dom
咱們會從一個簡單的點贊功能講起。 假設如今咱們須要實現一個點贊、取消點讚的功能。函數
若是你對前端稍微有一點了解,你就順手拈來:
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()
這裏很是暴力地使用了 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 裏面去就行了。
下一節《React.js 小書 Lesson3 - 前端組件化(二):優化 DOM 操做》中咱們繼續優化這個例子,讓它更加通用。