文章目錄
1、state & props
2、組件生命週期
3、事件處理
4、條件渲染
一、state & props
上一篇文章中,學習了React的組件化,但是沒有仔細研究props,特意將其放到這裏跟state一起學習,因爲它兩很像而且容易混淆。
function Hello(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Hello name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
回顧之前學習的組件的概念。上述過程中:
1、我們先是封裝了一個名爲Hello
的組件,裏面包含了<h1>
這個子組件
2、自定義組件<Hello >
,const element = <Hello name="Sara" />
這一句我理解爲類似於一個函數調用實例化的過程
3、使用render()
渲染這個組件
這裏面我們分析一下porps
,在這個組件渲染的過程中,我們發現Hello組件會接收一個傳遞進來的參數,最後我們在頁面上打印的內容:Hello { name },這個name是一個變量,是我們調用函數實例化的時候外部傳進來的參數。
到這裏,我們或許就能理解React組件化,複用的一些設計思想。即當我們想在網頁中顯示Hello Lisa、Hello Jone、Hello James等等等等,我們就不在需要去寫這麼多h1
標籤,直接調用我們的Hello組件,並且將 name 參數傳遞進組件當中就可以了。
下面再看看class聲明的組件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Lisa"/>;
ReactDOM.render(
element,
document.getElementById('root')
);
我們發現,class中獲取參數的時候,需要使用this
關鍵字去獲取到我們的參數{name:"Lisa"}
自定義組件格式: <組件名 參數名=參數值>
1、props
1.1 規則
(1)只讀性:
props經常被用作渲染組件和初始化狀態,當一個組件被實例化之後,它的props是隻讀的,不可改變的。如果props在渲染過程中可以被改變,會導致這個組件顯示的形態變得不可預測。只有通過父組件重新渲染的方式纔可以把新的props傳入組件中。
(2)數據流方向
參數通過父組件傳遞到子組件中
1.2 默認值
我們可以通過組件類的 defaultProps 屬性爲 props 設置默認值
class HelloMessage extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
HelloMessage.defaultProps = {
name: 'Lisa'
};
const element = <HelloMessage/>;
ReactDOM.render(
element,
document.getElementById('example')
);
2、state
React 把組件看成是一個狀態機。通過與用戶的交互,實現不同狀態,然後渲染 UI,讓用戶界面和數據保持一致。React 裏,只需更新組件的 state,然後根據新的 state 重新渲染用戶界面即可。總而言之,它是一種動態變化的狀態,它改變就會重新渲染組件的UI界面。
2.1 規則
可以改變
state是可以被改變的。不過,不可以直接通過this.state=
的方式來修改,而需要通過this.setState()
方法來修改state
。setState()
採用merge的方式修改state,會重新調用render()刷新UI,直接通過this.state=‘xxx’的方式也會修改state但是不會重新渲染。
2.2 this.setState()
使用這個方法:改變state的狀態後,重新渲染組件。類似於一個刷新功能
2.3 例子
接下來通過一個 定時器組件 例子來掌握它
(1)聲明 定時器 組件
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>現在時間: {這裏將顯示具體時間}.</h2>
</div>
);
}
}
(2)參數傳遞:我們需要將實時的時間傳遞進來,使用props?設想一下,假如我們使用props傳遞外部參數進來,我們的流程就是每次在外部獲取到當前時間後,再調用組件傳參,再渲染,這個過程中,我們無疑多了很多不必要的調用組件傳參的步驟。我們的想法應該是在組件內部自己實時刷新當前時間,這樣我們只需調用一次組件即可。
(3)我們使用 this.state
使用方法:
1、添加一個類構造函數來初始化狀態 this.state,
2、類組件應始終使用 props 調用基礎構造函數。
3、
constructor(props) {
super(props);
this.state = xxxx;
}
class Clock extends React.Component {
/*
構造函數初始化state
Class 組件應該始終使用 props 參數來調用父類的構造函數。
*/
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')
);
(4)至此,我們渲染這個組件的時候,會顯示出當前時間,然而,它並沒有進行實時刷新。下面我們需要涉及到一個組件生命週期的內容(組件生命週期)
我們將通過組件的生命週期函數來進行實時刷新
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
/ * 當組件渲染完畢的時候執行 */
componentDidMount() {
}
/ * 當組件卸載的時候執行*/
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
(5)設置計時器
/* 當組件渲染完成時,設置計時器,該計時器函數會每隔1秒執行 tick 函數*/
componentDidMount() {
this.timerID = setInterval(
/* 箭頭函數 */
() => this.tick(),
1000
);
}
/* 該函數會刷新state的值爲當前時間,並且執行 setState()方法 會對組件重新渲染*/
tick() {
this.setState({
date: new Date()
});
}
(6)銷燬計時器
/* 當組件被刪除的時候,將計時器也刪除*/
componentWillUnmount() {
clearInterval(this.timerID);
}
(7)完整計時器組件
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
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')
);
3、props、state總結
大部分組件的工作應該是從 props
裏取數據並渲染出來。但是,有時需要對用戶輸入、服務器請求或者時間變化等作出響應,這時才需要使用 state
。
嘗試把儘可能多的組件無狀態化。 這樣做能隔離 state,把它放到最合理的地方,也能減少冗餘並,同時易於解釋程序運作過程。
3.1、state
state的主要作用是用於組件保存、控制以及修改自己的狀態,它只能在constructor中初始化,它算是組件的私有屬性,不可通過外部訪問和修改,只能通過組件內部的this.setState來修改,修改state屬性會導致組件的重新渲染。
3.2、props
props理解爲從外部傳入組件內部的數據。由於React是單向數據流,所以props基本上也就是從服父級組件向子組件傳遞的數據。組件之間的通信
3.3、區別
(1)state
是組件自己管理數據,控制自己的狀態,可變;
(2)props
是外部傳入的數據參數,不可變;
二、組件生命週期
組件的生命週期講的是組件從創建到移除的一系列過程。
1、三個狀態
組件的生命週期有三個狀態:
(1)Mount
:插入真實 DOM(初始化階段)
(2)Update
:被重新渲染(更新階段)
(3)Unmount
:被移出真實 DOM(銷燬階段)
2、過程函數
2.1 初始化階段
這一階段包括組件的創建、實例化調用、完成渲染插入。
(1)componentWillMount()
組件初始化時調用,在整個生命週期中只調用一次;
(2)render()
是組件在創建虛擬dom,進行diff算法,更新dom樹
(3)componentDidMount()
是組件渲染結束之後調用。
2.2 更新階段
當組件的屬性或者狀態改變時會重新渲染
(1)shouldComponentUpdate()
是組件接受新的state或者props時調用,這是一個對性能優化非常重要的一個函數
(2)componentWillUpdata()
是在組件將要更新時才調用,可以修改state值
(3)render()
是組件執行渲染
(4)componentDidUpdate()
是組件更新完成後調用,此時可以獲取dom節點。
2.3 銷燬階段
當一個組件被移出Dom樹時,組件就會被卸載
(1)componentWillUnmount()
是組件將要卸載時調用,一些事件監聽和定時器需要在此時清除。
三、事件處理
React 元素的事件處理和 DOM 元素類似。但是有一點語法上的不同
3.1
(1)點擊觸發函數HTML 通常寫法是:
<button onclick="activateLasers()">
激活按鈕
</button>
(2)點擊觸發函數React 中寫法爲:
<button onClick={activateLasers}>
激活按鈕
</button>
3.2
在React當中,return false不會阻止事件的默認行爲,需要調用 e.preventDefault()
;
通常我們在 HTML 中阻止鏈接默認打開一個新頁面,可以這樣寫:
<a href="#" onclick="console.log('點擊鏈接'); return false">
點我
</a>
而在React中要這樣寫:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('鏈接被點擊');
}
return (
<a href="#" onClick={handleClick}>
點我
</a>
);
}
3.3 事件的this綁定
這一部分還是以具體的demo來學習,根據官方文檔的demo,我們要實現一個按鈕,按鈕初始爲開,點一次就開,再點一次又關。要實現這樣一個開關按鈕。
(1)語法一:在構造函數中bind(this)
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 爲了在回調中使用 `this`,這個綁定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
大概意思就是,我們點擊一次之後,下一次點擊要想使用上一次點擊時的this對象,就要在構造函數中進行一個綁定。
當然如果我們不想綁定那麼可以使用一種叫做實驗性語法?,大概就是實現上下文綁定的不同做法吧。
(2)語法二: handleClick = () => { 執行代碼 }
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
}
handleClick = () => {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('example')
);
(3)語法3: <button onClick={() => this.handleClick()}>
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
}
handleClick(){
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={() => this.handleClick()}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('example')
);
此語法問題在於每次渲染 LoggingButton 時都會創建不同的回調函數。在大多數情況下,這沒什麼問題,但如果該回調函數作爲 prop 傳入子組件時,這些組件可能會進行額外的重新渲染。我們通常建議在構造器中綁定或使用 class fields 語法來避免這類性能問題。
大概就是建議使用(1)(2)這兩種語法吧。
3.4 、事件處理傳參
1 、箭頭函數
語法:<button onClick={(e) => this.clickMe(param, e)}>clickme</button>
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name:"Lisa",
age: 0
}
}
addOneClick(num,e){
e.preventDefault();
/* 更改年齡 */
this.setState({ age: this.state.age + num })
}
render() {
return <div>
/* 點擊 觸發函數addOneClick
傳遞num=2,e參數
*/
<a href="#" onClick={(e) => this.addOneClick(2, e)}>點我啊</a>
{this.state.name}今年{this.state.age}歲了!
</div>;
}
}
2、bind
通過 bind 方式向監聽函數傳參,在類組件中定義的監聽函數,事件對象 e 要排在所傳遞參數的後面
語法:<button onClick={this.clickMe.bind(this, id)}> clickme</button>
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name:"Lisa",
age: 0
}
}
addOneClick(num,e){
e.preventDefault();
this.setState({ age: this.state.age + num })
}
render() {
return <div>
<a href="#" onClick={this.addOneClick.bind(this,2)}>點我啊</a>
{this.state.name}今年{this.state.age}歲了!
</div>;
}
}
四、條件渲染
條件渲染,顧名思義是根據一些條件,渲染不同的組件。最常見的例子就是用戶登錄與否顯示不同的組件。
例如:
function UserGreeting(props) {
return <h1>歡迎您!用戶:某某某</h1>;
}
function GuestGreeting(props) {
return <h1>請先註冊。</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
/*判斷用戶是否登錄條件*/
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
<Greeting isLoggedIn={false} />,
document.getElementById('example')
);
4.1、不用if,用&&
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')
);
在 JavaScript 中,
true && expression
總是會返回 expression,
false && expression
總是會返回 false。
因此,如果條件是 true,&& 右側的元素就會被渲染,如果是 false,React 會忽略並跳過它。
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
/*unreadMessages.length > 0成立就渲染h2,否則跳過 */
4.2 三目運算符
使用 JavaScript 中的三目運算符 condition ? true : false
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b>
(1)isLoggedIn爲真:輸出currently
(2)isLoggedIn爲真:輸出not
4.3 阻止條件渲染
在極少數情況下,你可能希望能隱藏組件,即使它已經被其他組件渲染。若要完成此操作,你可以讓 render 方法直接返回 null,而不進行任何渲染。
下面的示例中,<WarningBanner />
會根據 prop 中 warn 的值來進行條件渲染。如果 warn 的值是 false,那麼組件則不會渲染:
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(state => ({
showWarning: !state.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')
);