react實戰系列 —— react 的第一個組件
react 的第一個組件
寫了 react 有一個半月,現在又有半個月沒寫了,感覺對其仍舊比較陌生。
本文分兩部分,首先聊一下 react 的相關概念
,然後不使用任何語法糖(包括 jsx)或可能隱藏底層技術的便利措施來構建 React 組件
。
Tip:從一項新技術的底層元素起步有利於使用者更好的長期使用它
跨平台
大部分 react 應用是在 Web 平台
上。而 React Native
和 React VR
這樣的項目則創造了 react 應用在其他平台上運行的可能
React 應用主要成分
組件
組件是 React 中最基本單元
。
組件通常對應用戶介面的一部分,比如導航。也可以擔任數據格式化等職責。
可以將任何東西作為組件,儘管並不是所有東西作為組件都有意義。
如果將整個介面作為組件,並且沒有子組件或進一步的細分,那麼對自己並沒有什麼幫助。倘若,將介面不同部分拆解成可以組合,復用的部分,卻很有幫助。
組件具有良好的封裝性
、復用性
和組合性
。有助於為使用者提供一個更簡單的方式來思考和構建用戶介面。使用 React 構建應用就像使用積木來搭建項目,而構建應用時有取之不盡的「積木」
將 UI 分解成組件可以讓人更輕鬆的處理應用不同的部分。
組件需要一起工作,也就是說組件可以組合起來形成新的組件。組件組合
也是 React 最強大的部分之一。
如果身處一個中大型團隊,可以將組件發布到私有註冊中心(npm 或者其他)
React 組件還有一個方面就是生命周期方法
。當組件經過其生命周期的不同時期時(掛在、更新、卸載等),可以使用可預測、定義良好的方法。
React 庫
React 核心庫與 react-dom 和 react-native 緊密配合,側重組件的規範和定義
。能讓開發者構建一個組件樹,該組件樹能夠被瀏覽器和其他平台所使用。
react-dom 就是一個渲染器
。針對瀏覽器環境和服務端渲染。
比如我們要將組件渲染到瀏覽器,就得用到 react-dom。
React Native
庫專註於原生平台,能夠為 ios、android 和其他平台創建 react 應用。
第三方庫
React 不自帶 http
等其他前端常用工具庫。開發者可以自由的選擇對於工作最好的工具。
react 的權衡
react 屬於 專一型
,主要關注 UI 試圖方面。
而 angular 屬於 通用型
,其內置了許多解決方案,例如 http 調用、路由、國際化、字元串和數字格式化…
Tip:通常一些優秀的團隊會用這兩種方式。
React 的創建主要用於 Facebook 的 UI 需求。雖然大多數的 web 應用在此範圍之內,但也有一些應用不在。
React 是一種抽象,也存在抽象的代價
。React 以特定的方式構建並通過 api 向外暴露,開發者會失去對底層的可見性
。當然 React 也提供了緊急出口
,讓開發者深入較低的抽象層級,仍然可以使用 jQuery,不過需要以一種兼容 React 的方式使用。
有時還需要為 React 的行事方式買單。或許會影響應用的小部分(即不太適合用 React 的方式來工作)
使用 React 時所做的權衡有助於使用者成為更好的開發者。
虛擬 Dom
React 旨在將複雜的任務簡單化,把不必要的複雜性從開發者身上剝離出來。
鼓勵開發者使用聲明式
的編程而非命令式,也就是開發者聲明組件在不同狀態下的行為和外觀即可,React 負責渲染以及更新 UI,並將性能做到恰到好處。從而讓研發人員騰出時間思考其他方面。
驅動這些的主要技術之一就是虛擬dom
。
Tip:有關虛擬dom 的介紹可以參考 vue 快速入門-虛擬dom
虛擬 Dom 不是我們關注的重點。這正是 React 簡單
的地方:開發者被解放出來,去關注最關注的部分。
React 的簡單、非固化
什麼使 React 成為大型團隊的寵兒?首先是簡單
,其次是非固化
。
簡單的技術讓人更容易理解和使用。
React 是一個非常輕量的庫,只關注應用的視圖。更加容易與使用者當前的技術集成,並在其他方面為使用者留下了選擇的空間。一些功能固化的框架和庫要求使用者要麼全盤接受要麼徹底不用。
簡單和非固化的特性,以及恰到好處的性能,讓它非常適合大大小小的項目。
組件間的關係
組件可以獨立存在,也可用來創建其他組件。人們認為組件可以創建很多不同類的關係,從某種意義這是對的。
但組件更多的是以靈活的方式被使用,應該關注其獨立性
和常常不帶任何負擔
,可組合使用。所以組件只關注其父母和孩子
,兄弟關係可以不管。
建立組件關係的過程對每個團隊或項目都不盡相同,組件關係也可能會隨時間而改變,我們可以不期望一次就建立完美,也無需太過擔心,因為 React 會讓我們的 UI 迭代沒那麼困難。
搭建組件的框架
首先我們將組件的框架寫好:
<body>
<div id="root">
<!-- 此元素的內容將替換為您的組件 -->
</div>
<!-- react 庫 -->
<script src="//unpkg.com/react@17/umd/react.development.js"></script>
<!-- 用於處理 Dom 的 react 包 -->
<script src="//unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<!-- 使用 PropTypes 進行類型檢查 -->
<script src="//unpkg.com/[email protected]/prop-types.js"></script>
<script>
const reactElem = React.createElement(
'h1',
{title: 'i am h1'},
'Hello, world!'
)
ReactDOM.render(
reactElem,
document.getElementById('root')
);
</script>
</body>
Tip:這是一個普通的 html 頁面,直接通過 vscode 的 Live Server 插件運行即可
運行後的網頁顯示 Hello, world!
。生成的元素結構如下:
<div id="root">
<h1 title="i am h1">Hello, world!</h1>
</div>
下面我們稍微分析一下這個頁面:
首先定義了一個 div 元素,接著引入三個包,作用如下:
react.js
,React 的核心庫,用於定義組件的規範react-dom.js
,渲染器,用於瀏覽器和服務端渲染,用於創建組件和管理組件prop-types.js
,傳遞給組件的數據做類型檢查
接著通過 React.createElement
創建一個 react 元素。
React.createElement(
type,
[props],
[...children]
)
Tip:react 元素是什麼?
- react 元素是創建起來開銷極小的
普通對象
- react 元素是構成 React 應用的最小磚塊。react 元素之於React如同DOM元素之於DOM,react 元素組成了虛擬 DOM
- react 組件是由 react 元素構成的
最後使用 ReactDOM.render
將 React 元素渲染到 div#root
中。
// 在提供的 container 里渲染一個 React 元素,並返回對該組件的引用
ReactDOM.render(element, container[, callback])
Tip:調用 react-dom 的 render() 方法來讓 React 將組建渲染出來,並對組件進行管理。
React 元素
React 元素
是你想讓 React 渲染的東西的輕量表示。它可以表示為一個 Dom 元素,上文我們已經用其創建了一個 h1
的 dom 元素。
有必要再來分析一下 createElement()
的參數:
// 創建並返回指定類型的新 React 元素。其中的類型參數既可以是標籤名字元串(如 'div' 或 'span'),也可以是 React 組件 類型 (class 組件或函數組件),或是 React fragment 類型。
React.createElement(
type,
[props],
[...children]
)
const reactElem = React.createElement(
'h1',
{title: 'i am h1'},
'Hello, world!'
)
type
,一個 html 標籤(”div”、”h1″)或 React 類props
,指定 html 元素上要定義哪些屬性或組件類的實例上可以使用哪些屬性children
,還記得 React 組件是可以組合的嗎?
一句話:React.createElement()
在問:
我在創建什麼?
,是 Dom 元素,是 React 組件,還是React fragment。我怎麼配置它?
它包含什麼?
假如我們需要在頁面顯示如下元素:
<div>
<h2>i am h2</h2>
<a href="www.baidu.com">go baidu</a>
<p>
<em>i am em element</em>
</p>
</div>
可以這麼寫:
const c = React.createElement
const reactElem2 = React.createElement(
'div',
{},
c('h2', {}, 'i am h2'),
c('a', {href: 'www.baidu.com'}, 'go baidu'),
c('p', {},
c('em', {}, 'i am em element')
)
)
虛擬 DOM 樹
React 是怎麼把那麼多 React.createElement
轉換成螢幕上看到的東西的?這裡得用到虛擬 dom。
虛擬 dom 和真實 dom 有著相似的結構。
為了從 React 元素中形成自己的虛擬 DOM 樹
,React 會對 React.createElement 的全部 children 屬性進行求值,並將結果傳遞給父元素。就像一個小孩反覆再問 X是什麼?
,直到理解 X 的每個細節,直到他能形成一棵完整的樹。
React 組件
看看這段程式碼,我們創建了一個 React 元素並將其放入 dom 中:
<script>
const c = React.createElement
const reactElem = React.createElement(
'div',
{},
c('h2', {}, 'i am h2'),
c('a', {href: 'www.baidu.com'}, 'go baidu'),
c('p', {},
c('em', {}, 'a am em element')
)
)
ReactDOM.render(
reactElem,
document.getElementById('root')
);
</script>
如果我們需要擴展 reactElem 的功能、樣式以及其他UI相關?這時可以使用組件
。
組件可以將這些有效的組織在一起。
所以,要真正構建東西,不僅僅需要 React 元素,還需要組件。
React 組件就像是 React 元素,但 React 組件擁有更多特性。React 組件是幫助將 React 元素和函數組織到一起的類
我們可以使用函數或 js 類
創建組件。
使用 es6 的 class 來定義組件。就像這樣:
class MyComponent extends React.Component {
// 必須定義 render()。否則會報錯:
// MyComponent(...): No `render` method found on the returned component instance: you may have forgotten to define `render`.
render() {
// 返回單個 React 元素或 React 元素的數組
return reactElem
}
}
通常需要至少定義一個 render() 方法,幾乎任何向螢幕顯示內容的組件都帶有 render 方法
Tip:那些不直接顯示任何東西而是修改或增強其他組件的組件(稱高階組件),後續再討論。
我們將上面示例改成組件形式:
<script>
class MyComponent extends React.Component{
render(){
const c = React.createElement
return React.createElement(
'div',
{},
c('h2', {}, this.props.cnt1),
c('a', {href: 'www.baidu.com'}, 'go baidu'),
// class 屬性在需要改為 className
c('p', {className: this.props.aClass},
c('em', {}, this.props.cnt2)
)
)
}
}
</script>
<script>
// createElement 第一個參數可以是標籤名字元串(如 'div' 或 'span'),也可以是 React 組件 類型 (class 組件或函數組件),或是 React fragment 類型
const App = React.createElement(MyComponent, {
cnt1: 'i am h2',
aClass: 'p-class',
cnt2: 'a am em element'
})
ReactDOM.render(
App,
document.getElementById('root')
);
</script>
生成的 html 如下:
<div>
<h2>i am h2</h2>
<a href="www.baidu.com">go baidu</a>
<p class="p-class">
<em>a am em element</em>
</p>
</div>
React 中通過 this.props
就能獲取傳遞給組件的屬性。
this.props 是怎麼來的
MyComponent 中沒有初始化 props 的程式碼,既然自己沒做,那麼肯定是父類
幫忙做了。
就像這樣:
<script>
// 父類
class Rectangle {
constructor() {
// 子類接收的參數,這裡 arguments 都能接收到
const args = Array.from(arguments)
// args= (3) [{…}, 'b', 'c']
console.log('args=', args)
this.props = args[0]
}
}
// 子類
class Square extends Rectangle {
render(){
// name= pjl
console.log('name=', this.props.name)
}
}
let square = new Square({name: 'pjl'}, 'b', 'c')
square.render()
</script>
如果需要自己寫 constructor ,則需要手動調用 super()
,否則會報錯。就像這樣:
// 控制台輸入如下程式碼,報錯
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// Uncaught ReferenceError:在訪問「this」或從派生構造函數返回之前,必須在派生類中調用超級構造函數
class A{}
class B extends A{
constructor(){
}
}
let b = new B()
Tip: 有關 super 更多介紹請看 這裡
constructor(props)
如果不初始化 state 或不進行方法綁定,則不需要為 React 組件實現構造函數。通常,構造函數僅用於以下兩種情況:
- 通過給 this.state 賦值對象來初始化內部 state。
- 為事件處理函數綁定實例
類型檢測
類組件能使用自定義屬性。
通過組件好像能創建自定義 html 元素,而且還能做得更多。
能力越大,責任也越大,我們需要使用一些方法來驗證所使用的屬性,防止缺陷、規劃組件所使用的數據種類。
在上面示例基礎上,我們增加類型檢測:
<script>
class MyComponent extends React.Component {
...
}
// 屬性類型
MyComponent.propTypes = {
cnt1: PropTypes.number,
// 註:函數不是 function,而是 func
// 函數類型,並且必填
func: PropTypes.func.isRequired
}
// 默認值
MyComponent.defaultProps = {
cnt1: 'defaultName'
}
</script>
類型檢測生效了。控制台報錯如下:
Warning: Failed prop type: Invalid prop `cnt1` of type `string` supplied to `MyComponent`, expected `number`
警告:失敗的屬性類型:提供給「MyComponent」的類型為「string」的屬性「cnt1」無效,應為「number」`
Warning: Failed prop type: The prop `func` is marked as required in `MyComponent`, but its value is `undefined`.
警告:失敗的屬性類型:屬性「func」在「MyComponent」中標記為必需,但其值為「undefined」。
除了控制台發出 Warning,頁面顯示仍舊正常。
這裡其實就是按照特定規定,給 MyComponent 類增加了兩個靜態成員,用於類型檢測。我們可以自己模擬一下,請看示例:
<script>
class Rectangle {
constructor() {
this.props = arguments[0]
// 模擬類型驗證
this.validate(this.constructor)
}
validate(subClass) {
Object.keys(subClass.propTypes).forEach(key => {
const propType = subClass.propTypes[key]
const type = typeof this.props[key]
if (type !== propType) {
console.error(`Warning: ${key} 屬性 - 期待類型是 ${propType},所傳入的類型確是 ${type}`)
}
})
}
}
class Square extends Rectangle {
render() {
}
}
Square.propTypes = {
name: 'string',
age: 'number'
}
let square = new Square({ name: 18, age: 'pjl' })
square.render()
</script>
類型檢測結果如下:
// 瀏覽器控制台輸出:
Warning: name 屬性 - 期待類型是 string,所傳入的類型確是 number
Warning: age 屬性 - 期待類型是 number,所傳入的類型確是 string
現在這麼寫有些零散,我們可以使用 static
語法來對其優化。就像這樣:
class Square extends Rectangle {
static propTypes = {
name: 'string',
age: 'number'
}
render(){}
}
Tip: 有關 static 更多介紹可以百度 mdn static
嵌套組件
我們已經創建了一個類組件,並傳入了一些屬性,現在我們可以嘗試嵌套組件。
前面我們已經提到,組件組合
是 React 中非常強大的功能。比如一個頁面,我們可以通過組件進行拆分,單獨開發,最終卻是需要將組件組合成一個頁面,否則就不好玩了。
將上面組件拆成兩個,稍作變動,程式碼如下:
<script>
class MyComponent extends React.Component {
render() {
const c = React.createElement
return React.createElement(
'div',
{ className: 'parent-class' },
c('h2', {}, this.props.cnt1),
// 核心是:this.props.children。
this.props.children
)
}
}
class MySubComponent extends React.Component {
render() {
const c = React.createElement
return React.createElement(
'div',
{ className: 'sub-class' },
c('a', { href: 'www.baidu.com' }, 'go baidu'),
c('p', { className: this.props.aClass },
c('em', {}, this.props.cnt2)
)
)
}
}
</script>
<script>
// createElement 第一個參數可以是標籤名字元串(如 'div' 或 'span'),也可以是 React 組件 類型 (class 組件或函數組件),或是 React fragment 類型
const App = React.createElement(MyComponent, {
cnt1: 'i am h2',
},
React.createElement('p', {}, 'i am p element'),
React.createElement(MySubComponent, {
aClass: 'p-class',
cnt2: 'a am em element'
}
))
ReactDOM.render(
App,
document.getElementById('root')
);
</script>
核心是 this.props.children
,每個組件都可以獲取到 props.children
。最終渲染 html 結構如下:
<div id="root">
<div class="parent-class">
<h2>i am h2</h2>
<!-- this.props.children -->
<p>i am p element</p>
<div class="sub-class">
<a href="www.baidu.com">go baidu</a>
<p class="p-class">
<em>a am em element</em>
</p>
</div>
<!-- /this.props.children -->
</div>
</div>
動態組件
現在我們已經為組件添加了 render 方法和一些 propTypes。上面示例也僅僅顯示一些靜態文案,但要創建動態組件
,遠不止這些。
React 提供了某些特殊方法,當 React 管理虛擬 dom 時,react 會按順序調用它們,render 方法只是其中之一。
狀態
狀態可以讓組件交互並鮮活起來。
Tip: 狀態其他特性如下:
- 狀態可以理解成事物在某一時刻的資訊。可以分成可變狀態和不可變狀態。簡單區分就是事物創建之後是否能變化?如果可以,則是可變狀態
- 通過 this.state 訪問的是可變狀態;通過 this.props 訪問的是不可變狀態
- state 和 props 是數據的傳輸工具,這些數據構成應用並使其有用。
- State 與 props 類似,但是 state 是私有的,並且完全受控於當前組件 —— 官網
- react 中的 props 用來接收父組件傳來的屬性,state 是私有屬性
- 應該在什麼時候使用 state?想要改變存儲在組件中的數據時。
下面我們使用一下 state,既然 state 是可變狀態,那麼我們就創建一個表單組件,裡面有一個 input,一個提交按鈕。
程式碼如下:
<script>
class MyComponent extends React.Component {
render() {
const c = React.createElement
return React.createElement(
'div',
{ className: 'parent-class' },
this.props.children
)
}
}
class MySubComponent extends React.Component {
constructor(props){
// 如果不需要使用 this.props,則可以是 super()
super(props)
// 在構造函數中訪問 this 之前,一定要調用 super(),它負責初始化 this
this.state = {age: 18}
}
render() {
const c = React.createElement
return React.createElement(
'form',
{ className: 'sub-class' },
c('p', { },
c('input', {type: 'text', value: this.state.age})
),
c('p', { },
c('input', {type: 'submit', value: 'submit'})
),
)
}
}
</script>
生成的表單也很簡單,狀態數據 age 也已經在 input 元素中成功顯示:
<div id="root">
<div class="parent-class">
<form class="sub-class">
<p><input type="text" value="18"></p>
<p><input type="submit" value="submit"></p>
</form>
</div>
</div>
現在需要專門的方法更新 state 中的數據。不能直接修改(例如 this.state.age = 19
),因為 React 需要跟蹤狀態,並保證虛擬 dom 和真實 dom 的同步。得通過 React 提供的特殊通道(this.setState()
) 來更新 React 類組件中的狀態。
setState 不會立即更新組件,React 會根據狀態變化批量更新以便使效率最大化,也就是說 React 會以它最高效的方法基於新狀態更新 dom,做到儘可能快。
Tip: 不要直接修改 state 的示例請看 這裡
事件與 React 如何協作
以前我們直接操作 dom,於是可以通過 addEventListener
註冊事件;現在不直接操作 dom,而是和 React 元素打交道,那麼 React 應該提供對應的事件機制,最好和我們之前的習慣相同,而 React 確實是這樣做的。
React 實現了一個合成事件系統作為虛擬 Dom 的一部分,它會將瀏覽器中的事件轉為 React 應用的事件。可以設置響應瀏覽器事件的事件處理器,就像通常用 js 那樣做就好。區別是 React 的事件是設置在 React 元素或組件自身上,而不是用 addEventListener。
React 能監聽瀏覽器中很多不同事件,涵蓋了幾乎所有的交互(點擊、提交、滾動等)
接下來我們就可以用來自這些事件(比如文本變化時的事件 onchange
)的數據來更新組件狀態。
接著上面示例,需求是:更改 input[type=text]
的值,對應 state 中的 age 也會同步,點擊 submit 能提交。
程式碼如下:
<script>
class MyComponent extends React.Component {
render() {
const c = React.createElement
return React.createElement(
'div',
{ className: 'parent-class' },
this.props.children
)
}
}
class MySubComponent extends React.Component {
constructor(props) {
// 如果不需要使用 this.props,則可以是 super()
super(props)
// 在構造函數中訪問 this 之前,一定要調用 super(),它負責初始化 this
this.state = { age: 18 }
// 自定義的方法,react 沒有幫我們處理 this。所以這裡需要我們自己綁定一下
this.handleTextChange = this.handleTextChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleTextChange(evt) {
// 不手動綁定 this,則為 undefined
// console.log('this', this)
const v = evt.target.value
// 用來自事件的數據更新組件狀態。否則介面是看不到 age 的最新值的
this.setState({ age: v })
}
// React 要阻止默認行為,必須顯式的使用 preventDefault
handleSubmit(evt) {
evt.preventDefault()
// 提交表單。state {age: 18}
console.log('提交表單。state', this.state)
}
render() {
const c = React.createElement
return React.createElement(
'form',
{
className: 'sub-class',
onSubmit: this.handleSubmit
},
c('p', {},
c('input', {
type: 'text',
value: this.state.age,
// React 事件的命名採用小駝峰式(camelCase),而不是純小寫。例如在 html 中通常都是小寫(onclick)
onInput: this.handleTextChange
})
),
c('p', {},
c('input', { type: 'submit', value: 'submit' })
),
)
}
}
</script>
函數傳遞數據
利用函數
可以將子組件的數據傳遞給父組件。核心程式碼如下:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(data) {
console.log('提交表單 data=', data)
}
render() {
const c = React.createElement
return React.createElement(
'div',
{ className: 'parent-class' },
React.createElement(MySubComponent, {
aClass: 'p-class',
cnt2: 'a am em element',
onFormSubmit: this.handleSubmit
})
)
}
}
class MySubComponent extends React.Component {
handleSubmit(evt) {
evt.preventDefault()
this.props.onFormSubmit(this.state)
}
}
數據流向
在 React 中,數據自頂向下流動,可以通過 props 向子組件傳遞資訊並在子組件中使用這些資訊。表明可以將子組件的數據存儲在父組件中,並從那裡將數據傳遞給子組件。做個實例來驗證一下,定義三個組件(A、B、C),結構如下:
<div class='componentA'>
<p class='componentB'> apple </p>
<button class='componentC'>add apple</button>
</div>
數據存在 AComponent 中,每點擊一次 CComponent 組件,就會要求 AComponent 增加一個 apple,渲染到頁面的 BComponent 組件也相應增加。
全部程式碼如下:
<script>
class AComponent extends React.Component {
constructor(props) {
super(props)
this.state = {apples: this.props.apples}
this.handleAddApple = this.handleAddApple.bind(this)
}
handleAddApple(data) {
this.setState({apples: [data, ...this.state.apples]})
}
render() {
const c = React.createElement
return c(
'div',
{ className: 'componentA' },
this.state.apples.map((item, index) => c(BComponent, {
content: item,
// 需要增加 key 屬性,否則報錯:
// Warning: Each child in a list should have a unique "key" prop.
key: index
})),
c(CComponent, {
onHandleAddApple: this.handleAddApple
})
)
}
}
class BComponent extends React.Component {
render() {
const c = React.createElement
return React.createElement(
'p',
{
className: 'componentB',
// key: this.props.key
},
this.props.content
)
}
}
class CComponent extends React.Component {
constructor(props) {
super(props)
this.handleAdd = this.handleAdd.bind(this)
}
// React 要阻止默認行為,必須顯式的使用 preventDefault
handleAdd(evt) {
this.props.onHandleAddApple('apple')
}
render() {
const c = React.createElement
return React.createElement(
'button',
{
className: 'componentC',
onClick: this.handleAdd
},
'add apple'
)
}
}
</script>
<script>
const App = React.createElement(AComponent, {
apples: ['apple']
},
)
ReactDOM.render(
App,
document.getElementById('root')
);
</script>
jsx
JSX 僅僅只是 React.createElement(component, props, ...children)
函數的語法糖 —— react 官網-深入 JSX
我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應該呈現出它應有交互的本質形式 —— react 官網-JSX 簡介
jsx 讓人編寫類似於(但不是) HTML 的程式碼。
將上面增加 apple 的例子改為 jsx。全部程式碼如下:
<body>
<div id="root"></div>
<script src="//unpkg.com/react@17/umd/react.development.js"></script>
<script src="//unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="//unpkg.com/[email protected]/prop-types.js"></script>
<!-- Babel 能夠轉換 JSX 語法 -->
<script src="//unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class AComponent extends React.Component {
constructor(props) {
super(props)
this.state = { apples: this.props.apples }
this.handleAddApple = this.handleAddApple.bind(this)
}
handleAddApple(data) {
this.setState({ apples: [data, ...this.state.apples] })
}
render() {
return <div className='componentA'>
{
this.state.apples.map(
(item, index) =>
(<BComponent content={item} key={index} />)
)
}
<CComponent onHandleAddApple={this.handleAddApple} />
</div>
}
}
class BComponent extends React.Component {
render() {
return <p className='componentB'>{this.props.content}</p>
}
}
class CComponent extends React.Component {
constructor(props) {
super(props)
this.handleAdd = this.handleAdd.bind(this)
}
handleAdd(evt) {
this.props.onHandleAddApple('apple')
}
render() {
return <button className="componentC" onClick={this.handleAdd}>add apple</button>
}
}
</script>
<script type="text/babel">
const App = <AComponent apples={['apple']} />
ReactDOM.render(
App,
document.getElementById('root')
);
</script>
</body>
jsx 除了類似於 HTML 且語法簡單,另一個好處是聲明式
和封裝
。通過將組成視圖的程式碼和相關聯的方法包含在一起,使用者創建了一個功能組。本質上,需要知道的有關組件的所有資訊都匯聚在此,無關緊要的東西都被隱藏起來,意味著使用者更容易的思考組件,並且更加清楚他們作為一個系統是如何工作的。
主要注意,JSX 不是 html,只會轉譯成常規 React 程式碼。它的語法和慣例也不完全相同,需要關注一些細微的差異(偶爾有些「破費思量之處」),比如:
- 自定義組件使用大寫字母開頭。用於區分自定義組件和原生 html
- 屬性表達式寫在大括弧內。例如
<AComponent apples={['apple']} />
- 省略一個屬性的值,jsx 會視為 true。要傳 false,必須使用屬性表達式
- 要在元素內部插入表達式的值,也必須使用大括弧
Tip:比如class 得換成 className,更多 jsx 語法規則請看 這裡