一、JSX語法
1、花括號 { } 把任意的 [JavaScript 表達式]嵌入到 JSX 中
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')
);
2、JSX可作表達式
可以在 if 語句或者是 for 循環(huán)中使用 JSX,用它給變量賦值,當做參數(shù)接收,或者作為函數(shù)的返回值
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
3、用 JSX 指定屬性值
(1)用雙引號 “” 來指定字符串字面量作為屬性值
(2)用花括號 {} 嵌入一個 JavaScript 表達式作為屬性值
在屬性中嵌入 JavaScript 表達式時,不要使用引號來包裹大括號。否則,JSX 將該屬性視為字符串字面量而不是表達式。對于字符串值你應該使用引號,對于表達式你應該使用大括號,但兩者不能同時用于同一屬性。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
4、用 JSX 指定子元素
(1)如果是空標簽,您應該像 XML 一樣,使用 />立即閉合它
(2)JSX 標簽可能包含子元素
const element = <img src={user.avatarUrl} />;
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
5、JSX 表示對象
Babel 將JSX編譯成 React.createElement() 調(diào)用。
//兩種形式等價
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
二、元素渲染
元素(Elements)是 React 應用中最小的構建部件(或者說構建塊,building blocks),不同于組件。
const element = <h1>Hello, world</h1>;
1、渲染一個元素到 DOM
要渲染一個 React 元素到一個 root DOM 節(jié)點,把它們傳遞給 ReactDOM.render() 方法:
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
2 、更新已渲染的元素
React 元素是不可突變的. 一旦你創(chuàng)建了一個元素, 就不能再修改其子元素或任何屬性。一個元素就像電影里的一幀: 它表示在某一特定時間點的 UI 。更新 UI 的唯一方法是創(chuàng)建一個新的元素, 并將其傳入 ReactDOM.render()方法.
3、React 只更新必需要更新的部分
React DOM 會將元素及其子元素與之前版本逐一對比, 并只對有必要更新的 DOM 進行更新, 以達到 DOM 所需的狀態(tài)。
三、組件(Components) 和 屬性(Props)
組件使你可以將 UI 劃分為一個一個獨立,可復用的小部件,并可以對每個部件進行單獨的設計。
1、函數(shù)式組件和類組件
//函數(shù)式組件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//類組件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
2、渲染組件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
- 我們調(diào)用了 ReactDOM.render() 方法并向其中傳入了 <Welcome name="Sara" /> 元素。
- React 調(diào)用 Welcome 組件,并向其中傳入了 {name: 'Sara'} 作為 props 對象。
- Welcome 組件返回 <h1>Hello, Sara</h1>。
- React DOM 迅速更新 DOM ,使其顯示為 <h1>Hello, Sara</h1>。
注意:
-
組件名稱總是以大寫字母開始。
<div /> 代表一個 DOM 標簽,而 <Welcome /> 則代表一個組件,并且需要在作用域中有一個 Welcome 組件。 - 組件必須返回一個單獨的根元素。這就是為什么我們添加一個 <div> 來包含所有 <Welcome /> 元素的原因。
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
3、提取組件
提取組件可能看起來是一個繁瑣的工作,但是在大型的 Apps 中可以回報給我們的是大量的可復用組件。一個好的經(jīng)驗準則是如果你 UI 的一部分需要用多次 (Button,Panel,Avatar),或者本身足夠復雜(App,F(xiàn)eedStory,Comment),最好的做法是使其成為可復用組件。
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
4、所有 React 組件都必須是純函數(shù),并禁止修改其自身 props
四、狀態(tài)(State) 和 生命周期
1、把函數(shù)式組件轉化為類組件
遵從以下5步, 把一個類似 Clock這樣的函數(shù)式組件轉化為類組件:
- 創(chuàng)建一個繼承自 React.Component類的 ES6 class 同名類。
- 添加一個名為 render() 的空方法。
- 把原函數(shù)中的所有內(nèi)容移至 render() 中。
- 在 render() 方法中使用 this.props 替代 props。
- 刪除保留的空函數(shù)聲明。
Clock 現(xiàn)在被定為類組件,而不是函數(shù)式組件。
類允許我們在其中添加本地狀態(tài)(state)和生命周期鉤子。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2、在類組件中添加本地狀態(tài)(state)
我們現(xiàn)在通過以下3步, 把date從屬性(props) 改為 狀態(tài)(state):
class Clock extends React.Component {
2、添加一個 類構造函數(shù)(class constructor)初始化 this
constructor(props) {
super(props);
this.state = {date: new Date()};
}
1、替換 render() 方法中的 this.props.date 為 this.state.date
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
3、移除 <Clock /> 元素中的 date 屬性
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
3、在類中添加生命周期方法
在一個具有許多組件的應用程序中,在組件被銷毀時釋放所占用的資源是非常重要的。
當 Clock 第一次渲染到DOM時,我們要設置一個定時器。 這在 React 中稱為 “掛載(mounting)” 。
當 Clock 產(chǎn)生的 DOM 被銷毀時,我們也想 清除該計時器 。 這在 React 中稱為 “卸載(unmounting)” 。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
1、this.props 由 React 本身設定, 而 this.state 具有特殊的含義,但如果需要存儲一些不用于視覺輸出的內(nèi)容,則可以手動向類中添加額外的字段。
如果在 render() 方法中沒有被引用, 它不應該出現(xiàn)在 state 中。
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
2、我們在componentWillUnmount()生命周期鉤子中取消這個計時器
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
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')
);
回顧一下該過程,以及調(diào)用方法的順序:
- 當 <Clock /> 被傳入 ReactDOM.render() 時, React 會調(diào)用 Clock組件的構造函數(shù)。 因為 Clock 要顯示的是當前時間,所以它將使用包含當前時間的對象來初始化 this.state 。我們稍后會更新此狀態(tài)。
- 然后 React 調(diào)用了 Clock 組件的 render() 方法。 React 從該方法返回內(nèi)容中得到要顯示在屏幕上的內(nèi)容。然后,React 然后更新 DOM 以匹配 Clock 的渲染輸出。
- 當 Clock 輸出被插入到 DOM 中時,React 調(diào)用 componentDidMount() 生命周期鉤子。在該方法中,Clock 組件請求瀏覽器設置一個定時器來一次調(diào)用 tick()。
- 瀏覽器會每隔一秒調(diào)用一次 tick()方法。在該方法中, Clock 組件通過 setState() 方法并傳遞一個包含當前時間的對象來安排一個 UI 的更新。通過 setState(), React 得知了組件 state(狀態(tài))的變化, 隨即再次調(diào)用 render() 方法,獲取了當前應該顯示的內(nèi)容。 這次,render() 方法中的 this.state.date 的值已經(jīng)發(fā)生了改變, 從而,其輸出的內(nèi)容也隨之改變。React 于是據(jù)此對 DOM 進行更新。
- 如果通過其他操作將 Clock 組件從 DOM 中移除了, React 會調(diào)用 componentWillUnmount() 生命周期鉤子, 所以計時器也會被停止。
4、正確地使用 State(狀態(tài))
setState() 有三件事是你應該知道的:
(1)用 setState() 設置狀態(tài):
this.setState({comment: 'Hello'});
(2)state(狀態(tài)) 更新可能是異步的:
React 為了優(yōu)化性能,有可能會將多個 setState() 調(diào)用合并為一次更新。
因為 this.props 和 this.state 可能是異步更新的,你不能依賴他們的值計算下一個state(狀態(tài))。
例如, 以下代碼可能導致 counter(計數(shù)器)更新失?。?/p>
// 錯誤
this.setState({
counter: this.state.counter + this.props.increment,
});
要解決這個問題,應該使用第 2 種 setState() 的格式,它接收一個函數(shù),而不是一個對象。該函數(shù)接收前一個狀態(tài)值作為第 1 個參數(shù), 并將更新后的值作為第 2個參數(shù):
要彌補這個問題,使用另一種 setState() 的形式,它接受一個函數(shù)而不是一個對象。這個函數(shù)將接收前一個狀態(tài)作為第一個參數(shù),應用更新時的 props 作為第二個參數(shù):
// 正確
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// 正確
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
(3)state(狀態(tài))更新會被合并
當你調(diào)用 setState(), React 將合并你提供的對象到當前的狀態(tài)中。
例如,你的狀態(tài)可能包含幾個獨立的變量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后通過調(diào)用獨立的 setState() 調(diào)用分別更新它們:
合并是淺合并,所以 this.setState({comments}) 不會改變 this.state.posts 的值,但會完全替換this.state.comments 的值。
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
5、數(shù)據(jù)向下流動
無論作為父組件還是子組件,它都無法獲悉一個組件是否有狀態(tài),同時也不需要關心另一個組件是定義為函數(shù)組件還是類組件。
這就是 state(狀態(tài)) 經(jīng)常被稱為 本地狀態(tài) 或 封裝狀態(tài)的原因。 它不能被擁有并設置它的組件 以外的任何組件訪問。
一個組件可以選擇將 state(狀態(tài)) 向下傳遞,作為其子組件的 props(屬性):
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
同樣適用于用戶定義組件:
<FormattedDate date={this.state.date} />
FormattedDate 組件通過 props(屬性) 接收了 date 的值,但它仍然不能獲知該值是來自于 Clock的 state(狀態(tài)) ,還是 Clock 的 props(屬性),或者是直接手動創(chuàng)建的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常稱為一個“從上到下”,或者“單向”的數(shù)據(jù)流。任何 state(狀態(tài)) 始終由某個特定組件所有,并且從該 state(狀態(tài)) 導出的任何數(shù)據(jù) 或 UI 只能影響樹中 “下方” 的組件。
如果把組件樹想像為 props(屬性) 的瀑布,所有組件的 state(狀態(tài)) 就如同一個額外的水源匯入主流,且只能隨著主流的方向向下流動。
五、處理事件
1、與普通HTML區(qū)別
(1) React 事件使用駝峰命名,而不是全部小寫。
(2)通過 JSX , 你傳遞一個函數(shù)作為事件處理程序,而不是一個字符串。
<button onClick={activateLasers}>
Activate Lasers
</button>
(3)在 React 中你不能通過返回 false 來阻止默認行為。必須明確調(diào)用 preventDefault 。
例如,對于純 HTML ,要阻止鏈接打開一個新頁面的默認行為,可以這樣寫:
<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>
);
}
(4)當使用一個 ES6 類定義一個組件時,通常的一個事件處理程序是類上的一個方法。
Toggle 組件渲染一個按鈕,讓用戶在 “ON” 和 “OFF” 狀態(tài)之間切換:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 這個綁定是必要的,使`this`在回調(diào)中起作用
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')
);
2、將參數(shù)傳遞給事件處理程序
在循環(huán)內(nèi)部,通常需要將一個額外的參數(shù)傳遞給事件處理程序。 例如,如果 id 是一個內(nèi)聯(lián) ID,則以下任一方式都可以正常工作:
等價的,分別使用 arrow functions 和 Function.prototype.bind
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上面兩個例子中,參數(shù) e 作為 React 事件對象將會被作為第二個參數(shù)進行傳遞。通過箭頭函數(shù)的方式,事件對象必須顯式的進行傳遞,但是通過 bind 的方式,事件對象以及更多的參數(shù)將會被隱式的進行傳遞。
六、條件渲染
1、在函數(shù)式組件中用if判斷
我們需要創(chuàng)建一個 Greeting 組件, 用來根據(jù)用戶是否登錄, 判斷并顯示上述兩個組件之一:
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// 修改為 isLoggedIn={true} 試試:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
2、元素變量
在接下來的例子中,我們將會創(chuàng)建一個有狀態(tài)組件,叫做 LoginControl 。
它將渲染 <LoginButton /> 或者 <LogoutButton /> ,取決于當前狀態(tài)。同時渲染前面提到的 <Greeting /> 組件:
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')
);
3、使用邏輯 && 操作符的內(nèi)聯(lián) if 用法
(1)可以在JSX中嵌入任何表達式,方法是將其包裹在花括號中
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')
);
(2)條件操作符 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>
);
}
七、列表(Lists) 和 鍵(Keys)
1、基本列表組件
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')
);
2、鍵(Keys)
鍵(Keys) 幫助 React 標識哪個項被修改、添加或者移除了。數(shù)組中的每一個元素都應該有一個唯一不變的鍵(Keys)來標識:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
(1)使用 keys 提取組件
keys 只在數(shù)組的上下文中存在意義。
例如,如果你提取一個 ListItem 組件,應該把 key 放置在數(shù)組處理的 <ListItem /> 元素中,不能放在 ListItem 組件自身中的 <li> 根元素上。
function ListItem(props) {
// 正確!這里不需要指定 key :
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正確!key 應該在這里被指定
<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')
);
(2)keys 在同輩元素中必須是唯一的
在數(shù)組中使用的 keys 必須在它們的同輩之間唯一。然而它們并不需要全局唯一。我們可以在操作兩個不同數(shù)組的時候使用相同的 keys
(3)鍵是React的一個內(nèi)部映射,但其不會傳遞給組件的內(nèi)部。
如果你需要在組件中使用相同的值,可以明確使用一個不同名字的 prop 傳入。