最近看了一本關(guān)于學(xué)習(xí)方法論的書,強調(diào)了記筆記和堅持的重要性。這幾天也剛好在學(xué)習(xí)React,所以我打算每天堅持一篇React筆記。
第一節(jié):安裝
嘗試React
可以在CodePen中嘗試React,但是既然選擇了React,那就直接下載一個腳手架吧。
創(chuàng)建單頁面應(yīng)用
使用官方腳手架可以快速的創(chuàng)建React單頁面應(yīng)用。它提供了開發(fā)環(huán)境,可以使用最新的js特性,提供了很好的開發(fā)方法以及能夠給發(fā)布版本做優(yōu)化。
npm install -g create-react-app
create-react-app hello-world
cd hello-world
npm start
需要注意的是,這個腳手架只是一個前端的腳手架,你可以使用任何的后臺。該腳手架使用了webpack,Babel和ESLint,但是你可以自行配置他們。
將React添加到現(xiàn)有應(yīng)用中
這種情況下,可以在原有應(yīng)用中,分離出部分單獨的界面,用React來嘗試。我們也推薦用構(gòu)建工具來開發(fā),現(xiàn)在的構(gòu)建工具都包含一下幾個工具:
- 包管理工具:Yarn或者npm,它們可以讓你盡情的安裝、升級三方庫
- 打包工具:webpack或者Browserify,它們可以讓你組件化開發(fā)、將資源打包、優(yōu)化加載時間
- 編譯器:Babel,它將最新的js語法編譯為瀏覽器兼容的版本。
安裝React
npm init
npm install --save react react-dom
//todo 了解yarn和npm的區(qū)別。
我只知道npm的使用方法,所以我這里我先選擇npm。Yarn和Npm都是使用npm的倉庫。
使用ES6和JSX
安裝Babel就可以了。Babel安裝指南說了如何在不同的環(huán)境中配置Babel。確認你安裝了babel-preset-react和babel-preset-es2015以及在.babelrc
中正確配置好了。
ES6和JSX的HELLO WORLD
強烈建議用webpack和Browserify,這樣你可以模塊開發(fā)。
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
這里將新元素渲染到了root元素中,所以html文件中必須包含這么一個元素。同理,你也可以將它渲染到通過其他Javascript UI庫生成的節(jié)點中。
開發(fā)版本和發(fā)布版本
開發(fā)版本,react提供了很多有用的警告信息。但是發(fā)布的時候,需要用到發(fā)布版本。
原文中羅列了Brunch,Browserify,Create React App,Rollup和webpack的優(yōu)化方法。這里只記錄Create React App和Webpack。
Create React App
如果使用了Create React App腳手架,使用npm run build
命令,會在build目錄中生成優(yōu)化版本。
Webpack
根據(jù)這篇配置指導(dǎo)來配置,要配置DefinePlugin
和UgligyJsPlugin
。
使用CDN加速
npm包里的dist目錄里包含了編譯好的庫。可以直接拿來用。CDN加速如下:
<script src="https://unpkg.com/react@15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>
這兩個文件只適用于開發(fā),沒有優(yōu)化處理。
適合發(fā)布版的優(yōu)化版本如下所示:
<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
如果想要使用特定版本的react,直接替換里邊的版本號15
就可以了
第二節(jié) Hello World
最小的React例子如下:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
在root節(jié)點渲染h1標簽
關(guān)于js
react是js庫,所以要掌握好js。這里推薦js知識給大家。ES6的語法也是可以使用的,但是要謹慎些。推薦學(xué)習(xí)下箭頭函數(shù)、模版字符串、let和const。
第三節(jié) JSX介紹
show U the code:
const element = <h1>Hello, world!</h1>;
這個不是html也不是字符串的家伙就是JSX了,它是js的擴展。JSX可能會讓你想到模版語言,但是它是不折不扣的js。JSX為React提供渲染所需的“元素”(element)。
在JSX中插入表達式
在JSX中插入js表達式需要用花括號{}括起來:
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
分行顯示,有利于閱讀;用小括號()括起來是為了防止編輯器自動插入分號;
JSX中制定屬性
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
以上兩種都是可以的:提供一個字符串或者一個表達式。需要注意的是,不要將表達式既用花括號包裹,又用引號包裹,那就出錯了。魚({})和熊掌(‘’)兼得,可能啥也沒有哈。(或許有個大bug)
JSX中指定子元素
const element = <img src={user.avatarUrl} />;
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
如果沒有子元素,標簽可以馬上關(guān)閉。如果有呢,就像html一樣用就好了。
注意:雖然JSX和html很像,但是React DOM 使用駝峰標記法轉(zhuǎn)化html的屬性。
例如:class
-->className
,tabindex
-->tabIndex
JSX 防止注入攻擊
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
將用戶輸入嵌入到JSX中是安全的。默認情況下,在渲染之前,React DOM會轉(zhuǎn)義所有嵌入的值。這保證了只能插入你程序中寫好的東西。還有所有的嵌入的都會轉(zhuǎn)換為字符串,有效的防止了XXS(cross-site-scripting)攻擊。
JSX 代表對象
babel會把JSX轉(zhuǎn)換為React.createElement()
,所以下面的代碼塊是一致的
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
還會幫你檢查代碼,最終會轉(zhuǎn)換成如下對象:
// Note: this structure is simplified
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
這些對象,就叫做React元素了。用來渲染視圖以及更新視圖。
提示:強烈建議你搜索下編輯器的“babel”語法方案(syntax scheme),它會讓你的JSX和ES6代碼高亮顯示。
第四節(jié) 渲染元素
筆記原文
React元素是構(gòu)建React應(yīng)用的最小代碼塊了。元素描述的就是要渲染的界面了。
const element = <h1>Hello, world</h1>;
React元素和html的dom是不一樣的,相比之下,Rect元素更加輕量級。
注意:元素和組件是不同的,組件是由元素構(gòu)成的。
將元素渲染到DOM中
<div id="root"></div>
你有一個id為root的div,通常React應(yīng)用都會渲染到這個根結(jié)點里。如果你是將React結(jié)合到現(xiàn)成的應(yīng)用里,你也可以有多個節(jié)點,用來渲染React元素。
調(diào)用ReactDOM.render()
將React元素渲染進DOM節(jié)點中。
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
更新已經(jīng)渲染的元素
React元素是不可變的。一旦創(chuàng)建了就不能再更改了。就像是電影里的一幀圖像,代表一個時間點固定的圖像。
以目前我們所學(xué)的,如果要更新界面,就要調(diào)用函數(shù)ReactDOM.render()
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
用setInterval()
回調(diào),每隔一秒鐘調(diào)用一次ReactDOM.render()
。
注意:實際上,大多數(shù)React應(yīng)用只會調(diào)用一次ReactDOM.render()
。通常我們用state來顯示動態(tài)UI。
React 只更新需要更新的
更新界面的時候RactDOM會和之前的元素進行比較,并且只會更新改變了的部分。所以上面的代碼只會更新時間,其他節(jié)點都不會更新。寫代碼的時候就只要考慮如何顯示,不用考慮怎么改變了。
第五節(jié) 組件以及屬性(props)
date:20170329
筆記原文
組件將這個界面分割成幾個獨立的,可以重用的部分。開發(fā)的時候,可以單獨的對一個模塊進行思考。從概念上來說,組件很像js函數(shù)。它們接收參數(shù)(props)并且返回需要渲染的React元素。
函數(shù)組件和類組件
我們可以像定義js函數(shù)一樣定義組件:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
我們也可以用ES6類的語法來定義組件。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
這兩者實現(xiàn)是都是一樣的,但是類組件更加強大些,而函數(shù)組件會簡介些。
渲染組件
之前我們一直渲染的DOM元素,如div。其實React也是可以呈現(xiàn)用戶定義的組件:
const element = <Welcome name="Sara" />;
當(dāng)React遇到用戶定義的標記時,會將屬性通過對象的方式傳遞給組件,這個對象就是props。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
以上代碼,一目了然,直接將Welcome的name屬性,封裝到props對象里,并且傳遞給組件,組件在內(nèi)部,就可以通過props對象,獲取到外邊傳遞過來的值。
警告:組件名稱首字母一定要大寫。小寫的是DOM原生標記,大寫的代表組件,并且需要將組件引入到作用域中。
組合組件
組件可以引用組件。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
通常新的React應(yīng)用只有一個App組件。但是如果你要把React結(jié)合到已存在的項目中,最好就是自底向上的方式開發(fā)。
警告:組件必須返回一個元素,所以上邊的例子需要用div包裹三個Welcome組件。
提取組件
不要擔(dān)心把組件分割為更小的組件。原文列舉了如何把評論界面提取出頭像組件,用戶信息組件。
細分組件保證了代碼重用和降低代碼復(fù)雜度。
屬性只讀
Props對象的值都是不能變化的。一種“純”的函數(shù)是不會改變輸入的函數(shù),而改變輸入?yún)?shù)的函數(shù)都是不純的函數(shù)。React是很靈活的框架,但是有一個很苛刻的條件:所有的react組件都必須像純函數(shù)一樣,不改變輸入的props。
界面是時常要變的,所以就有了State概念。state可以在用戶交互的時候,網(wǎng)絡(luò)響應(yīng)或者其他情況下,作出界面改變。
State和生命周期
date:20170330
筆記原文
這一節(jié)將用state實現(xiàn)時鐘組件。最終要求要實現(xiàn)的效果如下:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
原來的代碼如下:
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
state和props很相似,但是state是私有的,并且是控件自己維護的。之前我們說過的,類組件有一些另外的功能。state是只支持類組件的一種特性。
將函數(shù)組件轉(zhuǎn)化為類組件
通過以下5步可以將函數(shù)組件轉(zhuǎn)化為類組件。
- 創(chuàng)建一個名字相同的ES6類,該類繼承自
React.Component
。 - 添加一個名為
render()
的函數(shù)。 - 將函數(shù)體內(nèi)的代碼添加到
render()
函數(shù)里。 - 將代碼里的
props
替換為this.props
。 - 將剩下的函數(shù)體刪除。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
在類中添加State
- 將
render()
中的this.props.date
替換為this.state.date
: - 在類的構(gòu)造函數(shù)中初始化state
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
注意這里我們通過構(gòu)造函數(shù)來傳遞props
- 將原來代碼里將Clock的date屬性刪除,最后的代碼如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
在代碼里添加生命周期方法
本例中,需要在組件加載(mounting)好之后添加一個計時器,然后在卸載(unmounting)的時候?qū)⒂嫊r器停止。
componentDidMount()
和componentWillUnmount()
這兩個就是生命周期里的回調(diào)方法。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
這里,我們將timerID保存到this中。this.props
是React自身維護的,this.state
具有特殊的含義,一般都用在需要更新界面的地方。我們可以在類里邊隨意添加不需要在界面上顯示的數(shù)據(jù)。也就是說,不是在render()
中顯示的都不需要用state。
實現(xiàn)tick()
,代碼如下:
tick() {
this.setState({
date: new Date()
});
}
正確的使用State
這里強調(diào)3點關(guān)于setState()
的內(nèi)容
不要直接修改State
如果直接通過this.state.XXX=XXX
來修改內(nèi)容,界面是不會刷新的。用setState
方法。
State更新是異步的
如下的代碼是有問題。
this.setState({
counter: this.state.counter + this.props.increment,
});
this.state
和this.props
可能異步的更新的,所以不應(yīng)該依賴它們來計算新的state。
這里要修改的話,就把setState的參數(shù),從對象改為函數(shù),代碼如下所示:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
函數(shù)第一個參數(shù)是之前的state,第二個參數(shù)是更新之后的props。上面的函數(shù)是箭頭函數(shù),也可以普通函數(shù)。
State的更新是合并的
這個可以理解,不然,難道要每次更新就把所有的state都列出來嗎。
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
以上的代碼,都是各自跟新自己的屬性,其他的屬性都會毫發(fā)無傷的維持原樣。這就是合并的含義。
向下的數(shù)據(jù)流
父控件和子控件都不知道一個控件是包含狀態(tài)的還是不包含狀態(tài)的,也不關(guān)心是函數(shù)組件還是類組件。所以state只屬于自身控件的,其他控件都訪問不了。
一個組件可以通過props
和state
將數(shù)據(jù)傳遞到子控件中。
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
<FormattedDate date={this.state.date} />
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
FormattedDate并不關(guān)心props中的數(shù)據(jù)是來自哪里的,可能是state,props或者手寫的。
這里,數(shù)據(jù)流就是自頂向下或者說是單向的。
響應(yīng)事件
20170331
筆記原文
React響應(yīng)事件和DOM響應(yīng)事件是差不多的,但是有些語法差異:
* React的事件命名采用駝峰法,而不是小寫
* 在JSX中傳遞的是一個函數(shù),而不是字符串
<button onclick="activateLasers()">
Activate Lasers
</button>
<button onClick={activateLasers}>
Activate Lasers
</button>
另一個差異就是不能通過返回false來阻止事件,必須手動調(diào)用preventDefault
。
例如以下代碼可以防止a標簽打開新頁面:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
在React中,可以這么寫:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
這里e
是一個合成的事件,React的事件都是通過W3C 定義,所以你也不用擔(dān)心兼容性。詳情見SyntheticEvent.
React中不需要調(diào)用addEventListener
,只需要在元素初始化的時候提供一個監(jiān)聽函數(shù)。
如果你通過ES6類來定義組件,類中本身有一組模版函數(shù)可以使用。例如Toggle
組件可以讓用戶改變打開和關(guān)閉狀態(tài):
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
必須要注意的是JSX回調(diào)函數(shù)中的this
關(guān)鍵字。在js中,類方法并沒有默認綁定
到this中。所以如果忘了綁定,那么this.handleClick
的this
就是為定義的。
這個也不是React中的特性,而是js函數(shù)運行機理的一部分。
如果引用的時候不用()
,例如onClick={this.handleClick}
,那么你就應(yīng)該要綁定下。
兩種方法可以來綁定this
。第一種是采用試驗性功能屬性初始化語法(propery initializer syntax)
。
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
這個語法在官方腳手架Create-Reac-App中默認支持。
第二種方法是使用箭頭函數(shù)。
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
這個方法的弊端是每次渲染LoggingButton
的時候都會生成一個回調(diào)函數(shù)。多數(shù)情況下是不影響的。但是如果要通過props將回調(diào)函數(shù)傳遞給子控件的時候,就會造成多次渲染。為了避免這一類的性能問題,建議用第一種方法或者在構(gòu)造函數(shù)中初始化。
條件渲染
20170331
筆記原文
React中的條件渲染和JS中的條件渲染是一樣的。使用[if][js-if]或者條件操作符根據(jù)條件來呈現(xiàn)不同的狀態(tài)。
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
以上的代碼就是依靠isLoggedIn
屬性來判斷渲染不同的界面。
元素變量
可以通過變量來存儲元素,以達到部分界面更新,而其他界面不變的目的。
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
下面的代碼,我們生成了一個包含狀態(tài)的組件,命名為LoginControl
。
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
行內(nèi)if操作符結(jié)合&&操作符
你可以在JSX中結(jié)合任何的表達式,表達式需要用花括號包裹起來。這是語法。結(jié)合&&
可以實現(xiàn)條件渲染。
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
這個原因很簡單哈。短路與就是這么神奇的。true && expression
返回true
,false && expression
返回false
.因此,條件滿足了&&
后面的內(nèi)容就會渲染出來,否則就直接忽略了。
行內(nèi)If-Else條件操作符
另一個條件渲染的方法是JS的條件操作符condition?true:false
。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
如果看不清,就換一種樣式,看起來會明顯些。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
如果條件比較復(fù)雜的時候,記得把組件抽取出子組件。
隱藏組件
通過返回null
的方式,來阻止渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true}
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
render
函數(shù)返回null
的時候并不會觸發(fā)組件的生命周期方法。但是componentWillUpdate
和componentDidUpdate
還是會被調(diào)用到。
列表與鍵(key)
20170401
[筆記原文][]
我們先看看js如何改變一個數(shù)組,直接上代碼:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
在控制臺輸出[2,4,6,8,10]
,React中對數(shù)組中的[元素(element)][react-element]的處理也是如此。
渲染多個組件
還是使用map
函數(shù),遍歷numbers
數(shù)組,每次返回一個li
元素。最后我們把數(shù)組存儲在listItem
中。
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
然后我們將listItems
用<ul>
元素包裹起來,最后[渲染到DOM][react-render]中。
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
基本的列表組件
通常我們需要把在[組件][react-component]中渲染一個列表。
重構(gòu)以上的代碼:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
在跑這段代碼的時候,它會警告你需要給列表項提供一個鍵(key)。鍵的作用參看下一節(jié),我們修改這個問題之后的代碼如下:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
鍵(keys)
鍵是react用來判斷列表項是否有改變,添加以及刪除。所以我們在列表數(shù)據(jù)中,需要添加一個固定的項:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
在一個列表中最好指定一個唯一確定的數(shù)值。所以通常使用數(shù)據(jù)的ID就可以了。
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
如果沒有id,那也可以使用索引。
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
如果列表的數(shù)據(jù)順序會變的情況下,我們是不推薦使用索引的,因為速度慢。參看[關(guān)于鍵的高級進階][react-key-explanation]。
與鍵一起抽取組件
鍵只有在列表的上下文中才有意義。
例如,我們抽取了ListItem
組件,我們需要將鍵同時抽出,放置在ListItem
上,而不是<li>
標簽上。
錯誤示例:
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
正確示例:
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<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')
);
總之,map()
里的元素需要鍵。
在一個列表中鍵必須唯一
同一個列表的鍵必須唯一,但是在全局上,不同的列表就沒有限制了。
由于key是react的機制,key的值并不會傳遞到組件props里,所以如果需要使用數(shù)據(jù)的時候,要再傳遞一次。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
Post
組件可以獲取到props.id
,但是獲取不到props.key
。
在JSX中嵌入map()
之前的例子我們用一個變量來存儲列表元素。JSX是可以[嵌入任何表達式][react-embed-expressions]的,所以我們將map()
函數(shù)直接嵌入到JSX中。
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
這種方法很清晰,但是不能濫用。如果map()
中嵌入太多,就需要[抽取組件][react-extract-component]了
表單
date:20170402
筆記原文
表單和其他元素有些不同,因為表單包含了一些交互。通常我們會用js來控制提交信息。所以React提供了控制組件
控制組件
html中的表單組件通常都是自己維護用戶輸入,但是在React中,只能用setState()
。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
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}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
每當(dāng)表單控件變化的時候,就會觸發(fā)onChange事件,從而調(diào)用監(jiān)聽方法。在監(jiān)聽方法中,改變state中的值。
textarea標簽
React中,<textarea>
標簽也會有value
屬性,這樣使用起來會比較方便。
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
select 標簽
在html中,select能夠生成一個下菜單:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
注意,Coconut
這一項中,具有select
屬性來指定默認選擇。在React中,是value
屬性。所以我們只要更新state就可以了。
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange}>
<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>
);
}
}
總之,<input type="text">
,<textarea>
和<select>
都是類似的機制,
監(jiān)聽函數(shù)處理多個表單元素輸入
如果要實現(xiàn)這樣的功能,可以在表單元素里添加name
屬性,監(jiān)聽函數(shù)可以根據(jù)event.target.name
的值來作出不同的處理。
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>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
這里要留意下我們使用了ES6的計算屬性名稱(computed property name)。下面兩段代碼都是一樣的效果。
this.setState({
[name]: value
});
var partialState = {};
partialState[name] = value;
this.setState(partialState);
同時,由于setState()
自動的混合部分屬性,所以也只是更新變化的部分。
替換控制組件
如果有時候?qū)懣刂平M件太麻煩了,或者說要從非React代碼變更為React的時候,你也可以考慮下非控制組件,這是控制組件的替代技術(shù)。
玩轉(zhuǎn)State
date:20170404
筆記原文
通常很多組件都要使用同一條數(shù)據(jù),這時候,就要把數(shù)據(jù)提出來,放在這些控件最近的父組件中。
舉個溫度計例子:BoilingVerdict
組件接收一個溫度參數(shù),返回水是否會沸騰。
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
然后Calculator
組件渲染一個<input>
使用戶可以輸入溫度數(shù)據(jù),并且把溫度保存在this.state.temperature
中,然后渲染BoilingVerdict
組件,用來指示當(dāng)前溫度。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
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} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
添加第二個輸入框
新需求是添加一個華氏度的輸入框,要求華氏溫度和攝氏溫度同時變化。剛開始,我們從Calculator
中提取TemperatureInput
組件,添加scale
prop屬性,來指示溫度的單位。
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
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>
);
}
}
好了,我們可以重新渲染Calculator
了。
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
現(xiàn)在雖然有了兩個輸入框,但是數(shù)據(jù)還是不會更新,同時BoilingVerdict
也沒有顯示出來,因為輸入的溫度,都在TemperatureInput
里,
添加轉(zhuǎn)換功能
首先,寫幾個功能函數(shù),實現(xiàn)溫度互相轉(zhuǎn)換:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
提出State
目前,兩個TemperatureInput
控件里的數(shù)據(jù)都是在它們自己的控件中。這是滿足不了需求的。在React中,State共享是將數(shù)據(jù)提出到最近的父容器中。我們稱之為提出State
。我們需要將TemperatureInput
中的state提出到Calculator
。這樣,兩個輸入控件具有相同的數(shù)據(jù)源,能夠?qū)崿F(xiàn)同步變化的需求。
一步一步來解析如何實現(xiàn)這個功能。
第一步,將state
替換為prop
,從Calculator
傳遞數(shù)據(jù)到輸入控件中。
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
我們知道,props是只讀的。如果是state的話,直接調(diào)用this.setState()
就好了。但是prop的話就沒有辦法控制了。
在React中,類比之前的控制組件
,就像<input>
標簽有value
和onChange
屬性,所以TemperatureInput
可以從父控件Caculator
里傳入temperature
和onTemperatureChange
屬性,這樣,TemperatureInput
想要跟新溫度的時候,就可以通過調(diào)用this.props.onTemperatureChange
:
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
這里函數(shù)名稱是可以自己隨便定義的,而不是定死的。onTemperatureChange
作為回調(diào),是從父控件與溫度數(shù)據(jù)一同傳進來的。調(diào)用的時候,就會相應(yīng)的改變父容器的state,然后更新視圖。
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
在Calculator
組件中,將溫度和單位存儲在state中。這些state是從input
組件里提取出來的。這是滿足需求的最少數(shù)據(jù)集。例如,當(dāng)我們在攝氏度一欄輸入37,數(shù)據(jù)如下:
{
temperature: '37',
scale: 'c'
}
當(dāng)我們在華氏度里輸入212,數(shù)據(jù)如下:
{
temperature: '212',
scale: 'f'
}
我們之前存儲了兩個輸入值,但是是沒有必要的。我們完全可以從一個輸入里推出另一個數(shù)據(jù)。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(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={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
好了,功能實現(xiàn)了。我們總結(jié)下,我們具體做了哪些工作:
- 在
TemperatureInput
組件中,<input>
中指定onChange
的回調(diào)函數(shù) - 在上述
onChange
回調(diào)中,執(zhí)行父容器提供的回調(diào)this.props.onTemperatureChange()
,將變化值傳遞給父容器。 - 兩個輸入控件都有自己的回調(diào)函數(shù)
- 輸入控件的回調(diào)函數(shù)中,將子控件傳遞過來的新的數(shù)據(jù),通過
setState
來重新設(shè)置, - 根據(jù)當(dāng)前的溫度和單位,渲染界面
他山之石
React奉行的是單一數(shù)據(jù)源原則。如果數(shù)據(jù)可能要有變化,那就使用State。如果這個數(shù)據(jù)其他控件需要用到,那么就提出State。如果要在不同的控件中同步數(shù)據(jù),那么就要遵循單向數(shù)據(jù)流。
從數(shù)據(jù)上來說,如果一個數(shù)據(jù)可以從其他數(shù)據(jù)中計算得到,那么就不用保存在state中。這個例子中,我們只是保存了一個輸入的數(shù)據(jù)。
React提供了一套React調(diào)試開發(fā)工具來查看屬性和狀態(tài)更新,有利于調(diào)試代碼。
組合VS繼承
date:20170405
筆記原文
建議不用繼承來復(fù)用代碼,而是用組合模式。
控制
有些組件并不知道他們要包含的子組件。例如幻燈片和對話框就是這樣。
我們建議這類組件為這類組件創(chuàng)建特殊的prop屬性。
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
這使得其他組件可以通過在JSX中包含標簽來傳遞任何的子標簽。
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
在<FancyBorder>
里的任意JSX標簽都會通過{props.children}
屬性傳遞到FancyBorder
中。由于在FancyBorder
里的<div>
中渲染了{props.children}
,最后這些子標簽就出現(xiàn)在了頁面中。
有時候,我們需要在組件里定義插槽,通過插入不同的組件,就可以實現(xiàn)不同的效果。這樣就不用使用props.children了。
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 />
} />
);
}
這里我們像傳遞數(shù)據(jù)一樣,傳遞控件。
特殊化
有時候我們需要將一些組件特殊化。例如歡迎對話框
是特殊化的對話框
了。在React中,還是通過組合的方式,將一個一般性
的控件,渲染為特殊化
的控件。
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
組合也同樣適用于類組件:
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通過成百上千個組件構(gòu)成,但是還沒有用到需要用到繼承的情況。Props和組合已經(jīng)具有足夠的靈活性和安全性來自定義控件。記住,porps可以傳遞原型數(shù)據(jù),React元素和函數(shù)。
如果想要服用功能函數(shù),建議用單獨的js模塊來分離代碼,實現(xiàn)復(fù)用。
Think in React筆記
date:20170405
筆記原文
得益于react的模塊化,我們可以很方便的開發(fā)大型web應(yīng)用。
從設(shè)計稿開始
- 將設(shè)計稿根據(jù)功能分塊處理。根據(jù)單一性原則,分為不能再精簡的單一模塊。
- 用固定的數(shù)據(jù)生成靜態(tài)頁面,因為我們不需要思考交互。
- 簡單的頁面可以通過自頂向下的方式開發(fā),復(fù)雜的頁面可以通過自底向上的方式開發(fā)
- 這里不需要用到state,通過props將數(shù)據(jù)傳遞到模塊中。
- 確認數(shù)量最少但是能滿足條件的state
- DRY原則:能夠從元數(shù)據(jù)獲取到的信息,就不要提煉出state。如TodoList的長度,直接使用數(shù)組的長度,而不是提煉出新的state。
- 如何分辨需要state還是props:是否是父控件通過props傳遞進來的;是否是一成不變的信息;是否可以通過其他信息得到。
- 確認state應(yīng)該在哪里,react的數(shù)據(jù)是單向的,新手可以通過以下步驟去掌握它。
- 確認每個組件依賴的state
- 找到一個共同的父控件
- 共同父控件或者該父控件的父控件擁有這個state
- 如果你不知道將這個state放在哪里的時候,就新建一個控件,專門來維護這個state,然后將這個控件放置在共同父控件的上層
- 添加反向數(shù)據(jù)流,通過底層控件改變上層控件的狀態(tài)
這就是React
代碼寫的比平常多了,但是在構(gòu)建大項目的時候,你就會發(fā)現(xiàn)好處了