$ 前言
? 在《React系統(tǒng)性學(xué)習(xí)(上)》中我們主要學(xué)習(xí)了
- 什么是React
- JSX語(yǔ)法
- 元素渲染
- 組件(
Component
) 和 屬性 (Props
) - 狀態(tài)(
State
) 和 生命周期(lifeCircle
) - 處理事件
- 條件渲染
本文我們將繼續(xù)學(xué)習(xí)
- 列表(
List
) 和 鍵(keys
) - 表單(
Forms
) - 狀態(tài)提升(
Lifting State Up
) - 組合 VS 繼承 (
Composition vs inheritance
)
$ 版本聲明
? 本文使用版本 React v16.2.0
$ 列表 和 鍵
? 列表(List
)校镐, 鍵(Key
)
? 回顧一下在javascript
中如何轉(zhuǎn)換列表:在數(shù)組中使用map()
函數(shù)對(duì)numbers
數(shù)組中的每個(gè)元素依次執(zhí)操作
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled) // 2, 4, 6, 8, 10
? React 基本借鑒了以上寫法式撼,只不過(guò)將數(shù)組替換成了元素列表
多組件渲染
? 可以創(chuàng)建元素集合,并用一對(duì)大括號(hào) {}
在 JSX 中直接將其引用即可
? 下面蔚万,我們用 JavaScript 的 map()
函數(shù)將 numbers
數(shù)組循環(huán)處理州邢。對(duì)于每一項(xiàng)动壤,我們返回一個(gè) <li>
元素兢卵。最終蜈首,我們將結(jié)果元素?cái)?shù)組分配給 listItems
:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
)
? 再把整個(gè) listItems
數(shù)組包含到一個(gè) <ul> 元素,并渲染到DOM
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
)
基本列表組件
? 通常情況下肃晚,我們會(huì)在一個(gè)組件中渲染列表而不是直接放到root
上锚贱。重構(gòu)一下上例
function NumberList(props) {
const numbers = props.number;
const listItems = numbers.map((number) =>
<li>{number}</li>
)
return (
<ul>{listItems}</ul>
)
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={number} />,
document.getElementById('root')
)
? 當(dāng)運(yùn)行上述代碼的時(shí)候,將會(huì)受到一個(gè)警告:a key should be provided for list items
关串,要求應(yīng)該為元素提供一個(gè)鍵(注:min版本react無(wú)提示)拧廊。要去掉這個(gè)警告也簡(jiǎn)單,只需要在listItem
的每個(gè)li
中增加key
屬性即可悍缠,增加后的每個(gè)<li>
如下
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
? 當(dāng)創(chuàng)建元素列表時(shí)卦绣,“key” 是一個(gè)你需要包含的特殊字符串屬性耐量,那為什么要包含呢飞蚓?
鍵(Keys)
? 鍵Keys
幫助React標(biāo)識(shí)那個(gè)項(xiàng)被修改、添加或者移除了廊蜒。數(shù)組中的每一個(gè)元素都應(yīng)該有一個(gè)唯一不變的鍵來(lái)標(biāo)識(shí)趴拧。
?挑選key
最好的辦法是使用一個(gè)在它的同輩元素中不重復(fù)的表示字符串。多數(shù)情況下可以使用數(shù)據(jù)中的IDs
來(lái)作為Keys
山叮。但是還是會(huì)遇到?jīng)]有id
字段的數(shù)據(jù)著榴,這種情況你可以使用數(shù)據(jù)項(xiàng)的索引值
cosnt todoItems = todos.map((todo, index) =>
// 數(shù)據(jù)項(xiàng)沒(méi)有IDs時(shí)使用該辦法
<li key={index}>
{todo.text}
</li>
)
? 如果列表項(xiàng)可能被重新排序,這種用法存在一定的性能問(wèn)題屁倔,React會(huì)產(chǎn)生時(shí)間復(fù)雜度為O(n^3)的算法執(zhí)行脑又。因此優(yōu)先使用數(shù)據(jù)項(xiàng)本身的字段內(nèi)容來(lái)設(shè)置鍵
使用 Keys 提取組件
? Keys
只有在數(shù)組的上下文中存在意義。例如锐借,如果你提取了一個(gè)ListItem
組件问麸,應(yīng)該把key
放置在數(shù)組處理的<ListItem />
元素中,而不能放在ListItem
組件自身的<li>
根元素上钞翔。
?以下的用法就是錯(cuò)誤的
function ListItem(props) {
const value = props.value;
return (
// 錯(cuò)誤严卖!不需要再這里指定 key
<li key={value.toString()}> {value}</li>
)
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 錯(cuò)誤!key 應(yīng)該在這里指定:
<ListItem value={number} />
);
return (
<ul> {listItems} </ul>
);
}
? 應(yīng)該寫成如下
function ListItem(props) {
// 正確布轿!這里不需要指定 key :
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正確哮笆!key 應(yīng)該在這里被指定
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
keys 在同輩元素中必須唯一
? 在數(shù)組中使用的 keys 必須在它們的同輩之間唯一来颤。然而它們并不需要全局唯一。我們可以在操作兩個(gè)不同數(shù)組的時(shí)候使用相同的 keys :
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
? 【注意】鍵是一個(gè)內(nèi)部映射稠肘,他不會(huì)作為props
傳遞給組件內(nèi)部福铅,如果你需要在組件中使用到這個(gè)值,可以自定義一個(gè)屬性名將該值傳入到props
中项阴,如下例中我們定義了一個(gè)id
屬性傳入給props
.
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
? 在這個(gè)例子中本讥,我們能讀取props.id
,但是讀取不了props.key
直接在JSX中使用map()
? 在上例中我們先聲明了一個(gè)listItem
然后在jsx中引用鲁冯,然而我們也能在JSX
中直接引用拷沸,稱之為 內(nèi)聯(lián)map()
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{ numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
? 至于選用哪種風(fēng)格編寫,只要遵循代碼清晰易讀原則即可
?
$ 表單
&esmp; HTML 表單元素與 React 中的其他 DOM 元素有所不同薯演,因?yàn)楸韱卧刈匀坏乇A袅艘恍﹥?nèi)部狀態(tài)撞芍。例如,這個(gè)純 HTML 表單接受一個(gè)單獨(dú)的 name:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
? 該表單和 HTML 表單的默認(rèn)行為一致跨扮,當(dāng)用戶提交此表單時(shí)瀏覽器會(huì)打開一個(gè)新頁(yè)面序无。如果你希望 React 中保持這個(gè)行為,也可以工作衡创。但是多數(shù)情況下帝嗡,用一個(gè)處理表單提交并訪問(wèn)用戶輸入到表單中的數(shù)據(jù)的 JavaScript 函數(shù)也很方便。實(shí)現(xiàn)這一點(diǎn)的標(biāo)準(zhǔn)方法是使用一種稱為“受控組件(controlled components
)”的技術(shù)璃氢。
?
受控組件(Controlled Components)
? 在 HTML 中哟玷,表單元素如 <input>
,<textarea>
和 <select>
表單元素通常保持自己的狀態(tài)一也,并根據(jù)用戶輸入進(jìn)行更新巢寡。而在 React 中,可變狀態(tài)一般保存在組件的 state(狀態(tài)) 屬性中椰苟,并且只能通過(guò) setState()
更新抑月。
? 通過(guò)使 React 的 state 成為 “單一數(shù)據(jù)源原則” 來(lái)結(jié)合這兩個(gè)形式。然后渲染表單的 React 組件也可以控制用戶輸入之后的行為舆蝴。這種形式谦絮,其值由 React 控制的輸入表單元素稱為“受控組件”。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange(event) {
this.setState({value:event.target.value})
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label>
Name:
<input type="text" value={this.state.value}
onChange={this.handleChange.bind(this} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
}
? 設(shè)置表單元素的value
屬性之后洁仗,其顯示值將由this.state.value
決定层皱,以滿足react
狀態(tài)的同一個(gè)數(shù)據(jù)理念。每次鍵盤敲擊之后會(huì)執(zhí)行handleChange
方法以便更新React狀態(tài)京痢,顯示只也將隨著用戶的輸入而改變奶甘。
? 由于value
屬性設(shè)置在我們的表單元素上,顯示的值總是this.state.value
祭椰,以滿足state
狀態(tài)的同意數(shù)據(jù)理念臭家。由于 handleChange
在每次敲擊鍵盤時(shí)運(yùn)行疲陕,以更新React state
,顯示的值將更新為用戶的輸入
? 對(duì)于受控組件來(lái)說(shuō)钉赁,每一次state
的變化都會(huì)伴有相關(guān)聯(lián)的處理函數(shù)蹄殃。這使得可以直接修改或驗(yàn)證用戶的輸入。比如你踩,我們希望強(qiáng)制name
的輸入都是大寫字母诅岩,可以如下實(shí)現(xiàn)
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea標(biāo)簽
? 在 HTML 中,<textarea>
元素通過(guò)它的子節(jié)點(diǎn)定義了它的文本值:
<textarea>
Hello there, this is some text in a text area
</textarea>
? 在 React 中带膜,<textarea>
的賦值使用 value
屬性替代吩谦。這樣一來(lái),表單中 <textarea>
的書寫方式接近于單行文本輸入框 :
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an esay about your favorite DOM element.'
}
}
// ...
render() {
return (
<form onSubmit={this.handle}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
}
? 注意膝藕,this.state.value
在構(gòu)造函數(shù)中初始化式廷,所以這些文本一開始就出現(xiàn)在文本域中。
select 標(biāo)簽
? 在 HTML 中芭挽,<select>
創(chuàng)建了一個(gè)下拉列表用法如下
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
<select>
? html利用selected
默認(rèn)選中滑废,但在React中,不使用selected
袜爪,而是給<select>
標(biāo)簽中增加一個(gè)value
屬性蠕趁,這使得受控組件使用更加方便,因?yàn)槟阒恍枰乱惶幾兞考纯伞?/p>
class FlavorForm extends React.Component {
// ...
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange.bind(this)}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
? 總的來(lái)說(shuō)辛馆,這使 <input type="text">
俺陋, <textarea>
和 <select>
都以類似的方式工作 —— 它們都接受一個(gè) value
屬性可以用來(lái)實(shí)現(xiàn)一個(gè)受控組件。
多選select
? 使用多選select
時(shí)怀各,需要給select
標(biāo)簽增加value
屬性倔韭,同時(shí)給value
屬性賦值一個(gè)數(shù)組
<select multiple={true} value={['B', 'C']}>
?
# 利用e.target
合并多個(gè)輸入元素的處理事件
? 當(dāng)您需要處理多個(gè)受控的 input 元素時(shí),您可以為每個(gè)元素添加一個(gè) name 屬性瓢对,并且讓處理函數(shù)根據(jù) event.target.name 的值來(lái)選擇要做什么。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({ [name]: value });
}
render() {
return (
<form>
<label>
Is going:
<input name="isGoing" type="checkbox" checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<label>
Number of guests:
<input name="numberOfGuests" type="number" value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
ReactDOM.render(
<Reservation />,
document.getElementById('root')
);
? 注意這里使用ES6計(jì)算的屬性名稱語(yǔ)法來(lái)更新與給定輸入名稱相對(duì)應(yīng)的 state
(狀態(tài)) 鍵的辦法
this.setState({ [name]: value })
?
var partialState = {};
partialState[name] = value;
this.setState(partialState);
? 由于 setState()
自動(dòng)將部分狀態(tài)合并到當(dāng)前狀態(tài)胰苏,所以我們只需要調(diào)用更改的部分即可硕蛹。
受控 Input 組件的 null 值
?在 受控組件上指定值 prop 可防止用戶更改輸入,除非您希望如此硕并。 如果你已經(jīng)指定了一個(gè) value
法焰,但是輸入仍然是可編輯的,你可能會(huì)意外地把 value
設(shè)置為undefined
或 null
倔毙。
? 以下代碼演示了這一點(diǎn)埃仪。 (輸入首先被鎖定,但在短暫的延遲后可以編輯陕赃。)
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
?
$ 狀態(tài)提升 (Lifting State Up)
? 通常情況下卵蛉,同一個(gè)數(shù)據(jù)的變化需要幾個(gè)不同的組件來(lái)反映颁股。我們建議提升共享的狀態(tài)到它們最近的祖先組件中。為了更好的理解傻丝,從一個(gè)案例來(lái)分析
溫度計(jì)算器
? 在本案例中甘有,我們采用自下而上的方式來(lái)創(chuàng)建一個(gè)溫度計(jì)算器,用來(lái)計(jì)算在一個(gè)給定溫度下水是否會(huì)沸騰(水溫是否高于100C)
(1)創(chuàng)建一個(gè) BoilingVerdict
組件葡缰,用來(lái)判水是否會(huì)沸騰并打印
function Bioling Verdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>
}
return <p>The water would not boil.</p>
}
(2)有了判斷溫度的組件之后亏掀,我們需要一個(gè)Calculator
組件,他需要包含一個(gè)<input>
提供我們輸入文圖泛释,并在this.state.temperature
中保存值滤愕。另外,以上BoilingVerdict
組件將會(huì)獲取到該輸入值并進(jìn)行判斷
class Caculator extends React.Component {
constructor(props) {
super(props);
this.state = { temperature: '' };
}
handleChange(e) {
this.setState({ temperature: e.target.value });
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius;</legend>
<input
value={temperature}
onChange={this.handleChange.bind(this)} />
<BoilingVerdict celsius={parseFloat(templature)} />
</fieldset>
)
}
}
(3)現(xiàn)在我們實(shí)現(xiàn)了基礎(chǔ)的父子組件通信功能怜校,假設(shè)我們有這樣的需求:除了一個(gè)設(shè)施文圖的輸入之外该互,還需要有一個(gè)華氏溫度輸入,并且要求兩者保持自動(dòng)同步
? 我們從Calculator
中提取出TemperatureInput
韭畸,然后增加新的scale
屬性宇智,值可能是c
或f
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.state = { temperature: e.target.value }
}
handleChange(e) {
this.setState({ temperature: e.target.value });
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}</legend>
<input
value={temperature]
onChange={this.handleChange} />
</fieldset>
)
}
}
? 抽出TemperatureInput
之后,Calculator
組件如下
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
? 現(xiàn)在有了兩個(gè)輸入框胰丁,但這兩個(gè)組件是獨(dú)立存在的随橘,不會(huì)互相影響,也就是說(shuō)锦庸,輸入其中一個(gè)溫度另一個(gè)并不會(huì)改變机蔗,與需求不符
? 我們不能再Calculator
中顯示BoilingVerdict
, Calcultor
不知道當(dāng)前的溫度,因?yàn)樗窃?code>TemperatureInput 中隱藏的, 因此我們需要編寫轉(zhuǎn)換函數(shù)
(4)編寫轉(zhuǎn)換函數(shù)
? 我們先來(lái)實(shí)現(xiàn)兩個(gè)函數(shù)在攝氏度和華氏度之間轉(zhuǎn)換
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
functin toFahrenheit(celsius0 {
return (celsius * 9 / 5) + 32;
}
?接下來(lái)甘萧,編寫函數(shù)用來(lái)接收一個(gè)字符串temperature
和一個(gè)轉(zhuǎn)化器函數(shù)作為參數(shù)萝嘁,并返回一個(gè)字符串,這個(gè)函數(shù)在兩個(gè)輸入之間進(jìn)行相互轉(zhuǎn)換扬卷。為了健壯性牙言,對(duì)于無(wú)效的temperature
值,返回一個(gè)空字符串怪得,輸出保留三位小數(shù)
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
cosnt rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
? 其中咱枉,convert
取值為 toCelsius
或toFahrenheit
狀態(tài)提升
? 目前,兩個(gè) TempetureInput 組件都將其值保留在本地狀態(tài)中徒恋,但是我們希望這兩個(gè)輸入時(shí)相互同步的蚕断。但我們更新攝氏溫度輸入時(shí),華氏溫度輸入應(yīng)該反映并自動(dòng)更新入挣,反之亦然亿乳。
? 在React 中,共享state(狀態(tài))是通過(guò)將其移動(dòng)到需要的的組件的最接近的共同祖先組件來(lái)實(shí)現(xiàn)的径筏,這被稱之為狀態(tài)提升(Lifting State Up)葛假。我們將從TemperatureInput
中移除相關(guān)狀態(tài)本地狀態(tài)障陶,并將其移動(dòng)到Calculator
中
? 如果Calculator
擁有共享狀態(tài),那么他將成為兩個(gè)輸入當(dāng)前溫度的單一數(shù)據(jù)源
桐款。他可以指示他們具有彼此一致的值 咸这。由于兩個(gè)TemperatureInput
的組件的props
來(lái)自于同一個(gè)父級(jí)Calculator
組件,連個(gè)輸入將始終保持同步
? 讓我們來(lái)一步步實(shí)現(xiàn)這個(gè)過(guò)程
(1)將值挪出組件魔眨,用props
傳入
render() {
// const temperature = this.state.temperature;
const temperature = this.props.temperature;
}
? 我們知道媳维,props
是只讀的,因此我們不能根據(jù)子組件調(diào)用this.setState()
來(lái)改變它遏暴。這個(gè)問(wèn)題侄刽,在React中通常使用 受控的方式來(lái)解決。就像DOM<input>
一樣接收一個(gè)value
和onChange
prop
朋凉, 所以可以定制Temperature
接受來(lái)自其腹肌 Calculator
的 temperature
和 onTemperatureChange
:
thandleChange(e) {
// 之前是:this.setState({ temperature: e.target.value });
this.props.onTemperatureChange(e.target.value);
}
請(qǐng)注意州丹,之定義組件中的
templature
或onTemperatureChange
prop
(屬性)名稱沒(méi)有特殊的含義。我們可以命名為任何其他名字杂彭,就像命名他們?yōu)?code>value和onChange
墓毒。是一個(gè)和常見的慣例
? onTemperatureChange prop
和 temperature prop
一起由父級(jí)的Calculator
組件提供,他將通過(guò)修改自己的本地state
來(lái)處理數(shù)據(jù)變更亲怠,從而通過(guò)新值重新渲染兩個(gè)輸入∷疲現(xiàn)在我們的代碼如下
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale< this.prps.scale;
return (
<fieldset>
<legend>Enter temperature in {scalenames[scale]}</legend>
<input value={tempearature} onChange={this.handleChange.bind(this)} />
</fieldset>
)
}
}
? 我們將當(dāng)前輸入的 temperature
和scale
存儲(chǔ)在本地的state
中,這是我們沖輸入“提升”的state(狀態(tài))
团秽,他將作為連個(gè)輸入的“單一數(shù)據(jù)源”主胧。為了渲染這兩個(gè)輸入,我們需要知道的所有數(shù)據(jù)的最小表示习勤,如攝氏度輸入37踪栋,這時(shí)Calculator
組件狀態(tài)將是:
{
temperature: '37',
scale: 'c'
}
? 我們確實(shí)可以存儲(chǔ)兩個(gè)輸入框(攝氏度和華氏度)的值,但事實(shí)證明是不必要的图毕。我們只要存儲(chǔ)最近更改的輸入框的值夷都,以及他們所表示的度量衡(scale
)就足夠了。然后推斷出另一個(gè)值吴旋。這也是我們實(shí)現(xiàn)兩個(gè)輸入框保持同步的途徑
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = { temperature: '', scale: 'c' }
}
handleCelsiusChange(temperature) {
this.setState({ scale: 'c', temperature });
}
handleRahrenheitChange(temperature) {
this.setState({scale: 'f', temperature })
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsuis}
onTemperatureChange={this.handleCelsiusChange.bind(this)} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange.bind(this)} />
<BiolingVerdict celsius={parseFloat(celsius)} />
</div>
)
}
}
? 現(xiàn)在损肛,無(wú)論你編輯哪個(gè)輸入框,Calculator
中的 this.state.temperature
和 this.state.scale
都會(huì)更新荣瑟。其中一個(gè)輸入框獲取值,所以任何用戶輸入都被保留摩泪,并且另一個(gè)輸入總是基于它重新計(jì)算值笆焰。
? 讓我們回顧一下編輯輸入時(shí)會(huì)發(fā)生什么:
- React 調(diào)用在 DOM
<input>
上的onChange
指定的函數(shù)。在我們的例子中见坑,這是TemperatureInput
組件中的handleChange
方法嚷掠。 -
TemperatureInput
組件中的handleChange
方法使用 新的期望值 調(diào)用this.props.onTemperatureChange()
捏检。TemperatureInput
組件中的props(屬性)
,包括onTemperatureChange
不皆,由其父組件Calculator
提供贯城。 - 當(dāng)它預(yù)先呈現(xiàn)時(shí),
Calculator
指定了攝氏TemperatureInput
的onTemperatureChange
是Calculator
的handleCelsiusChange
方法霹娄,并且華氏TemperatureInput
的onTemperatureChange
是Calculator
的handleFahrenheitChange
方法能犯。因此,會(huì)根據(jù)我們編輯的輸入框犬耻,分別調(diào)用這兩個(gè)Calculator
方法踩晶。 - 在這些方法中,
Calculator
組件要求 React 通過(guò)使用 新的輸入值 和 剛剛編輯的輸入框的當(dāng)前度量衡 來(lái)調(diào)用this.setState()
來(lái)重新渲染自身 - React 調(diào)用
Calculator
組件的render
方法來(lái)了解 UI 外觀應(yīng)該是什么樣子枕磁《沈撸基于當(dāng)前溫度和激活的度量衡來(lái)重新計(jì)算兩個(gè)輸入框的值。這里進(jìn)行溫度轉(zhuǎn)換 - React 使用
Calculator
指定的新props
(屬性) 調(diào)用各個(gè)TemperatureInput
組件的render
方法计济。 它了解 UI 外觀應(yīng)該是什么樣子 - React DOM 更新 DOM 以匹配期望的輸入值茸苇。我們剛剛編輯的輸入框接收當(dāng)前值,另一個(gè)輸入框更新為轉(zhuǎn)換后的溫度沦寂。
^狀態(tài)提升經(jīng)驗(yàn)總結(jié)
? 在一個(gè) React 應(yīng)用中学密,對(duì)于任何可變的數(shù)據(jù)都應(yīng)該遵循“單一數(shù)據(jù)源”原則,通常情況下凑队,state
首先被添加到需要它進(jìn)行渲染的組件则果,然后如果其他的組件也需要它,你可以提升狀態(tài)到他們最近的祖先組件漩氨。你應(yīng)該依賴從上到下的數(shù)據(jù)流向西壮,而不是試圖在不同的組件中同步狀態(tài)。
? 提升狀態(tài)相對(duì)于雙向綁定方法需要寫更多的"模板"代碼叫惊,但是有個(gè)好處款青,他可以更方便的找到和隔離bugs
。由于熱河state
(狀態(tài))都"存活"若干個(gè)組件中霍狰,而且可以分別對(duì)其獨(dú)立修改抡草,所以發(fā)生錯(cuò)誤的可能性大大減少。另外蔗坯,你可以實(shí)現(xiàn)任何定制的邏輯來(lái)拒絕或者轉(zhuǎn)換用戶輸入康震。
? 如果某個(gè)東西可以從props(屬性)
或者state(狀態(tài))
得到,那么他可能不應(yīng)該在state
中宾濒。例如我們只保存最后編輯的temperature
和scale
腿短,而不是保存celsiusValue
和fahrenheitValue
。另一個(gè)輸入框的值總是在render()
中計(jì)算得到。這是我們對(duì)其進(jìn)行清除和四舍五入到其他字段的同事不會(huì)丟失其精度
? 當(dāng)你看到UI中的錯(cuò)誤橘忱,你可以使用React開發(fā)者工具來(lái)檢查props
赴魁,并向上遍歷樹,知道找到負(fù)責(zé)更新狀態(tài)的組件钝诚,這是你可以跟蹤到bug
的源頭:Monitoring State in React DevTools
?
$ 組合 VS 繼承
? 組合 Composition
vs 繼承Inheritance
? React 擁有一個(gè)強(qiáng)大的組合模型颖御,建議使用組合而不是繼承以實(shí)現(xiàn)代碼的重用
? 接下來(lái)同樣從案例觸發(fā)來(lái)考慮幾個(gè)問(wèn)題,新手一般會(huì)用繼承凝颇,然后這里推薦使用組合
包含
? 一些組件在設(shè)計(jì)前無(wú)法或者自己要使用什么子組件潘拱,尤其是在 Sidebar
和 Dialog
等通用的 “容器” 中比較常見
? 這種組件建議使用特殊的children prop
來(lái)直接傳遞子元素到他們的輸出中:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
// children 表示來(lái)自父組件中的子元素
{props.children}
</div>
)
}
? 這允許其他組件通過(guò)嵌套JSX傳遞任意子組件給他們,比如在父組件中有h1
和p
子元素
function WelcomeDialog() {
return (
<FancyBo9rder color="blue">
<h1 className="Dialog-title">Welcome</h1>
<p className="Dialog-message">Thank you for your visitiong</p>
</FancBorder>
)
}
? 在 <FancyBorder>
JSX 標(biāo)簽中的任何內(nèi)容被傳遞到FancyBorder
組件中祈噪,作為一個(gè) children prop(屬性)
泽铛。由于 FancyBorder
渲染{props.children}
到一個(gè) <div>
中,傳遞的元素會(huì)呈現(xiàn)在最終的輸出中辑鲤。
? 這是一種簡(jiǎn)單的用法盔腔,這種案例并不常見,有時(shí)候我們需要在一個(gè)組件中有多個(gè)“占位符”月褥,這種情況下弛随,你可以使用自定義的prop
屬性,而不是children
:
function Contacts() {
return <div className="Contacts" />;
}
function Chat() {
return <div className="Chat" />;
}
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">{props.left}</div>
<div className="SplitPane-right">{props.right}</div>
</div>
)
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
}
/>
)
}
? 如 <Contacts />
和 <Chat />
等 React
元素本質(zhì)上也是對(duì)象宁赤,所以可以將其像其他數(shù)據(jù)一樣作為 props(屬性) 傳遞使用舀透。
特例
? 有時(shí)候,我們考慮組件作為其它組件的“特殊情況”决左。例如愕够,我們可能說(shuō)一個(gè) WelcomeDialog
是 Dialog
的一個(gè)特殊用例。
? 在React中佛猛,也可以使用組合來(lái)實(shí)現(xiàn)惑芭,一個(gè)偏“特殊”的組件渲染出一個(gè)偏“通用”的組件,通過(guò) props(屬性)
配置它:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title"> {props.title} </h1>
<p className="Dialog-mesage"> {props.message} </p>
</FancyBorder>
)
}
function WelcomeDialog() {
return (
<Dialog title="Welcome" message="Thank you for your visiting">
)
}
? 這對(duì)于類定義的組件組合也同樣適用
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
?
如何看待繼承继找?
在 Facebook 遂跟,在千萬(wàn)的組件中使用 React,我們還沒(méi)有發(fā)現(xiàn)任何用例婴渡,值得我們建議你用繼承層次結(jié)構(gòu)來(lái)創(chuàng)建組件纤控。
? 使用 props(屬性)
和 組合已經(jīng)足夠靈活來(lái)明確才顿、安全的定制一個(gè)組件的外觀和行為糕珊。切記崖堤,組件可以接受任意的 props(屬性)
,包括原始值柠并、React 元素究飞,或者函數(shù)
? 如果要在組件之間重用非 U I功能置谦,我們建議將其提取到單獨(dú)的 JavaScript 模塊中堂鲤。組件可以導(dǎo)入它并使用該函數(shù)亿傅,對(duì)象或類,而不擴(kuò)展它瘟栖。
?
$ 后語(yǔ)
? React的顛覆性思想不同于之前的任何一個(gè)框架葵擎,掌握React這門技術(shù),會(huì)幫助你自己思考如何更高性能半哟、高效率的編程酬滤,這可能影響你方方面面和以后的任意一次編程經(jīng)歷。
? 本文中如有錯(cuò)誤之處寓涨,歡迎指正盯串。