從零開始的 React 教學 Part 1 - Hello World
Last updated on Oct 2, 2022 in ReactJS 前端框架 by Amo Chen ‐ 6 min read
近來常使用 React 開發前端,但無奈前端的開發工具五花八門,比較熟悉後端技術的開發者應該會相當不習慣,因此本文記錄我使用 React 開發前端的演進過程。
本文會從一個簡單而且不需要使用任何前端工具的 React 範例開始,然後慢慢地一個個地導入現今前端常用的開發工具(例如 Webpack, Babel) 等等,至於其他較深入的 React 功能則不多加贅述,希望可以讓其他與我相同不懂前端技術的朋友,在開始前端生涯之前有個好文章能夠參考。
本文環境
- Chrome 81
- React 16.13.1
p.s. React 有分 React JS 與 React Native, 本文皆是 React JS
React
React 是為了讓前端開發者能夠更方便、更有效率地開發使用者介面(User Interface, UI)而誕生的 Javascript 函式庫。
React 中最核心的概念是元件(Components),或稱組件。
前端開發者可以透過 React 將各種複雜的 UI 元件切成一個又一個小巧又獨立的組件,因此開發者可以視需求像疊樂高積木一樣,挑選組件進行組合。
例如登入按鈕可以是個組件,因此有需要登入按鈕的頁面,都可以將該組件放到頁面中即可,除了維持 UI 的一致性之外,也減少前端開發者一直開發重複的介面所造成的開發成本浪費。
但是,有些組件可能有狀態的需求,例如勾選欄(Checkbox)組件就得紀錄是否處於被勾選的狀態,因此 React 第 2 個核心概念為狀態(state),有了狀態之後就能讓組件能夠真正獨立運作。
男子漢的 React 開發法
Hello World
不需要任何前端開發工具的方法,就是直接在網頁上嵌入 CDN(Content Delivery Network) 上的 React.js 例如:
<html>
<title>React 範例</title>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<body>
<!-- 以下稍後實作 -->
</bod>
</html>
有基礎樣板之後, Hello World
是少不了的。
首先,我們先增加一個名為 app
的空 div ,做為 React 的渲染(render)區域,可以想像 React 會在該 div 區塊內自動為我們放上組件。
<html>
<title>React 範例</title>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<body>
<div id="app">
</div>
<!-- 以下稍後實作 -->
</bod>
</html>
設定好渲染區域之後,接著就能夠開發 HelloWorld 組件,這個組件十分單純就只有負責顯示一段文字,沒有任何狀態。
為了方便,我們直接在 app
div 下方用 <script src="app.js"></script>
將組件放在 app.js
供載入。
<html>
<title>React 範例</title>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<body>
<div id="app">
</div>
<script src="app.js"></script>
</bod>
</html>
app.js
的內容如下:
const e = React.createElement;
class HelloWorld extends React.Component {
render() {
return e('h1', {}, 'Hello World');
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(HelloWorld), domContainer);
上述程式第 1 行先將 React.createElement 函式指定給 e
,可以讓我們後續少打很多字。
React.createElement 作用是建立一個 React.element , React 中所指的 element 與 component 是有差別的。
簡而言之 element 是 React 應用中最小的組成單位,element 指的是你在畫面上想要看到東西,譬如一串文字可以是一個 element ,而組件被渲染到畫面上時也是以 element 的形式呈現(也可以說 element 由組件組成)。
Elements are the smallest building blocks of React apps.
而且 element 是 Immutable
的,一旦建立好一個 element ,我們就無法改變該 element 的屬性,唯一改變它的方法就是另外建立一個新的 element 再交由 React 負責更新。
Once you create an element, you can’t change its children or attributes.
第 3 行開始,我們運用 ES6 的 class 語法讓 HelloWorld
繼承 React.Component 以創造 1 個組件(Component) 。
接著實作 React 組件中專門用來進行渲染的方法 render()
,並在該方法回傳一個很單純的 <h1>Hello World</h1>
element 。
最後再透過 ReactDOM.render(e(HelloWorld), domContainer)
告訴 React 我們想將 HelloWorld
渲染於 <div id="app"></div>
內。
執行成功的話,可以在頁面上看到 Hello World 文字,如此就完成第 1 個 React 範例啦!
男子漢的 React Props & State
Props
男子漢的腳步不停歇!緊接著來到 React 一定要懂的 Props 以及 State.
既然是透過 React 開發組件,那麼組件肯定少不了像函式一樣可以放參數的功能,而這個功能就是 Props ,譬如我們想為 Hello World 文字後增加顯示一個 name ,這個 name 必須是組件參數,那麼該怎麼做?如下所示:
const e = React.createElement;
class HelloWorld extends React.Component {
render() {
return e('h1', {}, 'Hello World,' + this.props.name);
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(HelloWorld, {name: 'React'}), domContainer);
上述更改後的範例可以看到 return e('h1', {}, 'Hello World,' + this.props.name);
多了 this.props.name
的部分,代表該組件可以接受 1 個名稱為 name
的 Props 參數,如果想接受其他參數則是 this.props.<你想要的參數名>
的形式。
而將 name 傳進去 Props 的部分是 e(HelloWorld, {name: 'React'})
。 Props 是 React.createElement
所接受的第 2 個參數,它會幫我們將 name
丟進 HelloWorld
組件中,這也是為什麼能夠輕鬆透過 this.props.name
拿到 name 值的原因。
this.props.children
再來談談一個特殊的 props: children
.
在開發 Web 時很常會有區塊包區塊的情況出現,例如:
<div class="parent">
<div class="children">
</div>
</div>
上述情況,被包住的區塊稱為 children ,在 React 中也可以組件包組件,這時候被包住的組件就放在 this.props.children
中。
例如以下範例,其實是 <h1>Hello World, <i>React</i></h1>
組件包組件:
const e = React.createElement;
class ItalicText extends React.Component {
render() {
return e('i', {}, this.props.text)
}
}
class HelloWorld extends React.Component {
render() {
return e('h1', {}, ['Hello World, ', this.props.children]);
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(
e(HelloWorld, {},
e(ItalicText, {text: 'React'}
)
),
domContainer
);
上述範例最重要的部分是 return e('h1', {}, ['Hello World, ', this.props.children]);
,我們將 e()
的第 3 個參數改為陣列 ['Hello World', this.props.children]
,之所以需要改為陣列是由於 Hello World
是字串,而 this.props.children
是 element
,兩者沒辦法簡單透過 +
串接在一起,因此用陣列形式交給 React.createElement() , React 就會自動幫我們渲染。
State
最後談談與 Props 類似的 State 。
State 可以讓組件管理自己的狀態,讓組件具有能夠獨立運作的功能。
假設我們想將 HelloWorld 組件加上一個被點擊幾次的計數器,被點擊幾次的數值就很適合使用 state 進行實作,因為它完全屬於組件特有的狀態。
那麼,如何加上 state 呢?在組件的 constructor(props)
建構方法內加上 this.state
的宣告即可,例如以下範例宣告 count
在 state 內,我們就可以透過 this.state.count
取得 count
的數值:
const e = React.createElement;
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
}
}
render() {
return e('h1', {}, 'Hello World, ' + this.props.name + ' ' + this.state.count);
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(HelloWorld, {name: 'React'}), domContainer);
上述範例成功的話,就可以看到顯示結果 Hello World, React 0 。
setState
前述範例尚未實作點擊 Hello World 後更新數值的部分,本段將實作該功能。
首先 state 內的任何數值更新,一定要透過 setState() 方法更新,原因在於 React 是透過 setState()
方法得知 state 內的數值有更新,才會進一步更新畫面,所以一定要使用 setState()
方法更新 state 內的數值。
接著我們用 setState()
為 HelloWorld
組件加上更新數值的功能吧!
const e = React.createElement;
class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
}
this.addCount = this.addCount.bind(this)
}
addCount() {
let count = this.state.count + 1
this.setState({count: count})
}
render() {
return e('h1', {onClick: this.addCount},
'Hello World, ' + this.props.name + ' ' + this.state.count
);
}
}
const domContainer = document.querySelector('#app');
ReactDOM.render(e(HelloWorld, {name: 'React'}), domContainer);
首先說明第 12 - 15 行將原本 state 中的 count 取出 +1 後,再用 this.setState({count: count})
更新 state 中的數值。
觸發 +1 的部分則在第 18 行中傳進去的 Props {onClick: this.addCount}
, onClick
是 React 規定的 event 等同於 HTML 中的 onclick
attribute ,在 React 中一定要使用 onClick
代替。
p.s. 各種 event 可以在 SyntheticEvent 文件中找到。
傳進去的 onClick
props 會在我們點擊文字時觸發 this.addCount
,進而更新 state 中的 count
。
最後回到第 9 行解釋 this.addCount = this.addCount.bind(this)
的作用,少了這行的話,將會出現以下的錯誤訊息:
Uncaught TypeError: Cannot read property 'state' of undefined
原因在於自行定義的 property 存取不到 React 執行時的 context ,也就是 React 自己的 this
,所以我們需要在 constructor
時將 React 的 context 跟自定義的 property 進行綁定:
this.addCount = this.addCount.bind(this)
如此一來, addCount
就能夠存取 this.state
囉!
p.s. 關於 this
與 bind()
可以閱讀 MDN web docs - this
此部分也許有點難以理解,不過只要記住 render, constructor 這些 React 文件有提及的函式不需要綁定 bind(this)
之外,其他自定義的函式如果需要存取 React 內的 property 或者函式,就需要在 constructor 內 bind(this)
。
小結
礙於篇幅限制,本篇先介紹不用任何前端開發工具開發 React 的過程,下一篇將介紹用 Webpack 更有效率地進行開發。
References
https://reactjs.org/