1 react
未分類
1 jsx簡介
JSX是一種JavaScript的語法擴展,運用于React架構中,其格式比較像是模版語言,但事實上完全是在JavaScript內部實作的,元素是構成React應用的最小單位,JSX就是用來宣告React當中的元素,React使用JSX來描述用戶界面,
let text = <p>hello world</p>
它被稱為 JSX,是一個 JavaScript 的語法擴展,我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應該呈現出它應有互動的本質形式,JSX 可能會使人聯想到模版語言,但它具有 JavaScript 的全部功能,
2 為什么使用 JSX?
簡單來說,React 認為組件才是王道,而組件是和模板緊密關聯的,組件模板和組件邏輯分離讓問題復雜化了,顯而易見的道理,關鍵是怎么做?
所以就有了 JSX 這種語法,就是為了把 HTML 模板直接嵌入到 JS 代碼里面,這樣就做到了模板和組件關聯,但是 JS 不支持這種包含 HTML 的語法,所以需要通過工具將 JSX 編譯輸出成 JS 代碼才能使用,
3 在 JSX 中嵌入運算式
let name = 'jack'
let text = <p>hello world {name}</p>
ReactDOM.render(text, document.getElementById('example'))
在 JSX 語法中,你可以在大括號內放置任何有效的 JavaScript 運算式,例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 運算式,
let obj = {
name:"小王",
age:24,
className : "高三一班"
}
function getInfo() {
return `${obj.name}-----${obj.age}--------${obj.className}`
}
let text = <p>{getInfo()}</p>
ReactDOM.render(text, document.getElementById('example'))
4 JSX 也是一個運算式
在編譯之后,JSX 運算式會被轉為普通 JavaScript 函式呼叫,并且對其取值后得到 JavaScript 物件,
也就是說,你可以在 if 陳述句和 for 回圈的代碼塊中使用 JSX,將 JSX 賦值給變數,把 JSX 當作引數傳入,以及從函式中回傳 JSX:
let obj = {
name:"小王",
age:24,
className : "高三一班"
}
function getInfo(data) {
let text = null;
if (data.age > 20){
text = <h1>我的年紀可以耍朋友了</h1>
}else {
text = <h1>我還沒成年不能耍朋友</h1>
}
return text
}
ReactDOM.render(getInfo(obj), document.getElementById('example'))
¥¥ 5 JSX 特定屬性
你可以通過使用引號,來將屬性值指定為字串字面量:
<h1 id="name">我的年紀可以耍朋友了</h1>
也可以使用大括號,來在屬性值中插入一個 JavaScript 運算式:
let a = 'http://www.baidu.com';
let element = <a href={a}>跳轉百度</a>
ReactDOM.render(element, document.getElementById('example'))
在屬性中嵌入 JavaScript 運算式時,不要在大括號外面加上引號,你應該僅使用引號(對于字串值)或大括號(對于運算式)中的一個,對于同一屬性不能同時使用這兩種符號,
警告:
因為 JSX 語法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小駝峰命名)來定義屬性的名稱,而不使用 HTML 屬性名稱的命名約定,
例如,JSX 里的 class 變成了 className,而 tabindex 則變為 tabIndex,
5 使用 JSX 指定子元素
假如一個標簽里面沒有內容,你可以使用 /> 來閉合標簽,就像 XML 語法一樣:
let a = 'https://img0.baidu.com/it/u=2346062104,1318622495&fm=26&fmt=auto&gp=0.jpg';
let element = <img src={a}/>
ReactDOM.render(element, document.getElementById('example'))
JSX 標簽里能夠包含很多子元素:
let a = 'https://img0.baidu.com/it/u=2346062104,1318622495&fm=26&fmt=auto&gp=0.jpg';
let element = <div>
<h2>這是標題</h2>
<div>
<p>這是文章的征文</p>
<a href={a}>跳轉百度></a>
</div>
</div>
ReactDOM.render(element, document.getElementById('example'))
7 JSX 表示物件
Babel 會把 JSX 轉譯成一個名為 React.createElement() 函式呼叫,
以下兩種示例代碼完全等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement() 會預先執行一些檢查,以幫助你撰寫無錯代碼,但實際上它創建了一個這樣的物件:
// 注意:這是簡化過的結構
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
這些物件被稱為 “React 元素”,它們描述了你希望在螢屏上看到的內容,React 通過讀取這些物件,然后使用它們來構建 DOM 以及保持隨時更新,
2 元素渲染
元素是構成 React 應用的最小磚塊,
const element = <h1>Hello, world</h1>;
與瀏覽器的 DOM 元素不同,React 元素是創建開銷極小的普通物件,React DOM 會負責更新 DOM 來與 React 元素保持一致,
1 將一個元素渲染為 DOM
let element = <div>
<h2 id="name">這是標題</h2>
</div>
ReactDOM.render(element, document.getElementById('example'))
我們將其稱為“根” DOM 節點,因為該節點內的所有內容都將由 React DOM 管理,
想要將一個 React 元素渲染到根 DOM 節點中,只需把它們一起傳入 ReactDOM.render():
let element = <div>
<h2 id="name">這是標題</h2>
</div>
ReactDOM.render(element, document.getElementById('example'))
2 更新已渲染的元素
React 元素是不可變物件,一旦被創建,你就無法更改它的子元素或者屬性,一個元素就像電影的單幀:它代表了某個特定時刻的 UI,
根據我們已有的知識,更新 UI 唯一的方式是創建一個全新的元素,并將其傳入 ReactDOM.render(),
function dom() {
let element = <div>
<h2 id="name">這是標題</h2>
</div>
ReactDOM.render(element, document.getElementById('example'))
}
setInterval(dom, 1000)
這個例子會在 setInterval() 回呼函式,每秒都呼叫 ReactDOM.render(),在實踐中,大多數 React 應用只會呼叫一次 ReactDOM.render(),
3 React 只更新它需要更新的部分
React DOM 會將元素和它的子元素與它們之前的狀態進行比較,并只會進行必要的更新來使 DOM 達到預期的狀態,
function dom() {
let element = <div>
<p>{new Date().toLocaleTimeString()}</p>
</div>
ReactDOM.render(element, document.getElementById('example'))
}
setInterval(dom, 1000)
盡管每一秒我們都會新建一個描述整個 UI 樹的元素,React DOM 只會更新實際改變了的內容
3 組件 & Props
組件允許你將 UI 拆分為獨立可復用的代碼片段,并對每個片段進行獨立構思,
組件,從概念上類似于 JavaScript 函式,它接受任意的入參(即 “props”),并回傳用于描述頁面展示內容的 React 元素,
1 函陣列件與 class 組件
function don(props) {
return <p>{props.name}</p>
}
函式是一個有效的 React 組件,因為它接收唯一帶有資料的 “props”(代表屬性)物件與并回傳一個 React 元素,這類組件被稱為“函陣列件”,因為它本質上就是 JavaScript 函式,
你同時還可以使用 ES6 的 class 來定義組件:
class dom extends React.component{
render() {
return (
<div>
{this.poops.name}
</div>
);
}
}
2 渲染組件
之前,我們遇到的 React 元素都只是 DOM 標簽:
let element =<p>React</p>
不過,React 元素也可以是用戶自定義的組件:
const element = <welcome name="Sara"></welcome>
當 React 元素為用戶自定義組件時,它會將 JSX 所接收的屬性(attributes)以及子組件(children)轉換為單個物件傳遞給組件,這個物件被稱之為 “props”,
function Dom(props) {
return <p>我的名字是:{props.name}</p>
}
let element = <Dom name="jack"/>
ReactDOM.render(
element,
document.getElementById('example')
)
執行程序
我們呼叫 ReactDOM.render() 函式,并傳入 作為引數,
React 呼叫 Dom 組件,并將 {name: 'jack'} 作為 props 傳入,
Dom 組件將
我的名字是:jack
我的名字是:jack
3 組合組件
組件可以在其輸出中參考其他組件,這就可以讓我們用同一組件來抽象出任意層次的細節,按鈕,表單,對話框,甚至整個螢屏的內容:在 React 應用程式中,這些通常都會以組件的形式表示,
function Dom(props) {
return <p>我的名字是:{props.name}</p>
}
function App(){
return <div>
<Dom name="jack"/>
<Dom name="Alice"/>
<Dom name="make"/>
</div>
}
ReactDOM.render(
<App/>,
document.getElementById('example')
)
通常來說,每個新的 React 應用程式的頂層組件都是 App 組件,但是,如果你將 React 集成到現有的應用程式中,你可能需要使用像 Button 這樣的小組件,并自下而上地將這類組件逐步應用到視圖層的每一處,
4 提取組件
function Compent(props) {
return (
<div className="info">
<div children="userInfo">
<img src={props.user.src}/>
<p>{props.user.name}</p>
</div>
<div>
{props.text}
</div>
<div>
{formatDate(props.data)}
</div>
</div>
)
}
function formatDate(data) {
return data
}
let user = {
src:"https://img1.baidu.com/it/u=1080718765,3168440202&fm=26&fmt=auto&gp=0.jpg",
name:"jack"
}
ReactDOM.render(
<Compent user={user} text="這是一個測驗文本" data="'2021-10-90"></Compent>,
document.getElementById('example')
)
該組件由于嵌套的關系,變得難以維護,且很難復用它的各個部分,因此,讓我們從中提取一些組件出來,
function Compent(props) {
return (
<div className="info">
<div children="userInfo">
<UserText user={props.user}></UserText>
</div>
<div>
{props.text}
</div>
<div>
{formatDate(props.data)}
</div>
</div>
)
}
function UserInfo(props) {
return (
<img src={props.user.src}/>
)
}
function UserText(props) {
return (
<div>
<UserInfo user={props.user}></UserInfo>
<p>{props.user.name}</p>
</div>
)
}
function formatDate(data) {
return data
}
let user = {
src:"https://img1.baidu.com/it/u=1080718765,3168440202&fm=26&fmt=auto&gp=0.jpg",
name:"jack"
}
ReactDOM.render(
<Compent user={user} text="這是一個測驗文本" data="'2021-10-90"></Compent>,
document.getElementById('example')
)
最初看上去,提取組件可能是一件繁重的作業,但是,在大型應用中,構建可復用組件庫是完全值得的,根據經驗來看,如果 UI 中有一部分被多次使用(Button,Panel,Avatar),或者組件本身就足夠復雜(App,FeedStory,Comment),那么它就是一個可復用組件的候選項,
5 Props 的只讀性
組件無論是使用函式宣告還是通過 class 宣告,都決不能修改自身的 props,
function sum(a, b) {
return a + b;
}
這樣的函式被稱為“純函式”,因為該函式不會嘗試更改入參,且多次呼叫下相同的入參始侄訓傳相同的結果,
相反,下面這個函式則不是純函式,因為它更改了自己的入參:
function withdraw(account, amount) {
account.total -= amount;
}
所有 React 組件都必須像純函式一樣保護它們的 props 不被更改,
4 State & 生命周期
我們將學習如何封裝真正可復用的組件
可以從封裝時鐘的外觀開始:
function Time(props) {
return(
<div>
<p>當前的時間是</p>
<p>{props.data.toLocaleTimeString()}</p>
</div>
)
}
setInterval(() => {
ReactDOM.render(
<Time data ={new Date()}></Time>,
document.getElementById('example')
)
}, 1000)
然而,它忽略了一個關鍵的技術細節:Clock 組件需要設定一個計時器,并且需要每秒更新 UI,
理想情況下,我們希望只撰寫一次代碼,便可以讓 Clock 組件自我更新:
function Time(props) {
return(
<div>
<p>當前的時間是</p>
<p>{props.data.toLocaleTimeString()}</p>
</div>
)
}
ReactDOM.render(
<Time data ={new Date()}></Time>,
document.getElementById('example')
)
我們需要在 Clock 組件中添加 “state” 來實作這個功能,
State 與 props 類似,但是 state 是私有的,并且完全受控于當前組件,
1 將函陣列件轉換成 class 組件
- 通過以下五步將 Clock 的函陣列件轉成 class 組件:
- 創建一個同名的 ES6 class,并且繼承于 React.Component,
- 添加一個空的 render() 方法,
- 將函式體移動到 render() 方法之中,
- 在 render() 方法中使用 this.props 替換 props,
-
洗掉剩余的空函式宣告,
class Time extends React.Component { render() { return ( <div> <p>當前的時間是</p> <p>{this.props.data.toLocaleTimeString()}</p> </div> ) } }現在 Time 組件被定義為 class,而不是函式,
每次組件更新時 render 方法都會被呼叫,但只要在相同的 DOM 節點中渲染 ,就僅有一個 Time 組件的 class 實體被創建使用,這就使得我們可以使用如 state 或生命周期方法等很多其他特性,
2 向 class 組件中添加區域的 state
我們通過以下三步將 date 從 props 移動到 state 中:
把 render() 方法中的 this.props.date 替換成 this.state.date :
class Time extends React.Component {
render() {
return (
<div>
<p>當前的時間是</p>
<p>{this.state.data.toLocaleTimeString()}</p>
</div>
)
}
}
添加一個 class 建構式,然后在該函式中為 this.state 賦初值:
class Time extends React.Component {
constructor(props) {
super(props);
this.props = {data: new Date()}
}
render() {
return (
<div>
<p>當前的時間是</p>
<p>{this.state.data.toLocaleTimeString()}</p>
</div>
)
}
}
通過以下方式將 props 傳遞到父類的建構式中:
constructor(props) {
super(props);
this.state = {data: new Date()}
}
Class 組件應該始終使用 props 引數來呼叫父類的建構式,
-
移除 元素中的 date 屬性:
class Time extends React.Component { constructor(props) { super(props); this.state = {data: new Date()} } render() { return ( <div> <p>當前的時間是</p> <p>{this.state.data.toLocaleTimeString()}</p> </div> ) } } ReactDOM.render( <Time></Time>, document.getElementById('example') )
3 將生命周期方法添加到 Class 中
在具有許多組件的應用程式中,當組件被銷毀時釋放所占用的資源是非常重要的,
當 Clock 組件第一次被渲染到 DOM 中的時候,就為其設定一個計時器,這在 React 中被稱為“掛載(mount)”,
同時,當 DOM 中 Clock 組件被洗掉的時候,應該清除計時器,這在 React 中被稱為“卸載(unmount)”,
class Time extends React.Component {
constructor(props) {
super(props);
this.state = {data: new Date()}
}
componentDidMount(){
console.log(1)
}
componentWillUnmount(){
console.log(2)
}
render() {
return (
<div>
<p>當前的時間是</p>
<p>{this.state.data.toLocaleTimeString()}</p>
</div>
)
}
}
ReactDOM.render(
<Time></Time>,
document.getElementById('example')
)
這些方法叫做“生命周期方法”,
componentDidMount() 方法會在組件已經被渲染到 DOM 中后運行,所以,最好在這里設定計時器:
componentDidMount(){
this.timeIdD= setInterval(() => this.time(), 1000)
}
接下來把計時器的 ID 保存在 this 之中(this.timerID),
盡管 this.props 和 this.state 是 React 本身設定的,且都擁有特殊的含義,但是其實你可以向 class 中隨意添加不參與資料流(比如計時器 ID)的額外欄位,
我們會在 componentWillUnmount() 生命周期方法中清除計時器:
componentWillUnmount(){
clearInterval(this.timeIdD)
}
最后,我們會實作一個叫 tick() 的方法,Clock 組件每秒都會呼叫它,
使用 this.setState() 來時刻更新組件 state:
class Time extends React.Component {
constructor(props) {
super(props);
this.state = {data: new Date()}
}
componentDidMount(){
this.timeIdD= setInterval(() => this.time(), 1000)
}
componentWillUnmount(){
clearInterval(this.timeIdD)
}
time(){
this.setState({
data: new Date()
})
}
render() {
return (
<div>
<p>當前的時間是</p>
<p>{this.state.data.toLocaleTimeString()}</p>
</div>
)
}
}
ReactDOM.render(
<Time></Time>,
document.getElementById('example')
)
讓我們來快速概括一下發生了什么和這些方法的呼叫順序:
-
當 被傳給 ReactDOM.render()的時候,React 會呼叫 Time 組件的建構式,因為 Time
需要顯示當前的時間,所以它會用一個包含當前時間的物件來初始化 this.state,我們會在之后更新 state, -
之后 React 會呼叫組件的 render() 方法,這就是 React 確定該在頁面上展示什么的方式,然后 React 更新 DOM
來匹配 Time 渲染的輸出, -
當 Time 的輸出被插入到 DOM 中后,React 就會呼叫 ComponentDidMount()
生命周期方法,在這個方法中,Time 組件向瀏覽器請求設定一個計時器來每秒呼叫一次組件的 time() 方法, -
瀏覽器每秒都會呼叫一次 time() 方法, 在這方法之中,Time 組件會通過呼叫 setState() 來計劃進行一次 UI
更新,得益于 setState() 的呼叫,React 能夠知道 state 已經改變了,然后會重新呼叫 render()
方法來確定頁面上該顯示什么,這一次,render() 方法中的 this.state.date
就不一樣了,如此以來就會渲染輸出更新過的時間,React 也會相應的更新 DOM, -
一旦 Time 組件從 DOM 中被移除,React 就會呼叫 componentWillUnmount()
生命周期方法,這樣計時器就停止了,
4 正確地使用 State
關于 setState() 你應該了解三件事:
1 不要直接修改 State
例如,此代碼不會重新渲染組件:
this.state.data = new Date()
而是應該使用 setState():
this.setState({
data: new Date()
})
建構式是唯一可以給 this.state 賦值的地方:
2 State 的更新可能是異步的
出于性能考慮,React 可能會把多個 setState() 呼叫合并成一個呼叫,
因為 this.props 和 this.state 可能會異步更新,所以你不要依賴他們的值來更新下一個狀態,
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要解決這個問題,可以讓 setState() 接收一個函式而不是一個物件,這個函式用上一個 state 作為第一個引數,將此次更新被應用時的 props 做為第二個引數:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
上面使用了箭頭函式,不過使用普通的函式也同樣可以:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
3 State 的更新會被合并
當你呼叫 setState() 的時候,React 會把你提供的物件合并到當前的 state,
例如,你的 state 包含幾個獨立的變數:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后你可以分別呼叫 setState() 來單獨地更新它們:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
這里的合并是淺合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替換了 this.state.comments,
4 資料是向下流動的
不管是父組件或是子組件都無法知道某個組件是有狀態的還是無狀態的,并且它們也并不關心它是函陣列件還是 class 組件,
這就是為什么稱 state 為區域的或是封裝的的原因,除了擁有并設定了它的組件,其他組件都無法訪問,
組件可以選擇把它的 state 作為 props 向下傳遞到它的子組件中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
這對于自定義組件同樣適用:
<FormattedDate date={this.state.date} />
FormattedDate 組件會在其 props 中接收引數 date,但是組件本身無法知道它是來自于 Clock 的 state,或是 Clock 的 props,還是手動輸入的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
FormattedDate 組件會在其 props 中接收引數 date,但是組件本身無法知道它是來自于 Clock 的 state,或是 Clock 的 props,還是手動輸入的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常會被叫做“自上而下”或是“單向”的資料流,任何的 state 總是所屬于特定的組件,而且從該 state 派生的任何資料或 UI 只能影響樹中“低于”它們的組件,
5 事件處理
React 元素的事件處理和 DOM 元素的很相似,但是有一點語法上的不同:
- React 事件的命名采用小駝峰式(camelCase),而不是純小寫,
-
使用 JSX 語法時你需要傳入一個函式作為事件處理函式,而不是一個字串,
例如,傳統的 HTML:<button onclick="activateLasers()"> Activate Lasers </button>在 React 中略微不同:
<button onClick={activateLasers}> Activate Lasers </button>在 React 中另一個不同點是你不能通過回傳 false 的方式阻止默認行為,你必須顯式的使用 preventDefault ,例如,傳統的 HTML 中阻止鏈接默認打開一個新頁面,你可以這樣寫:
<a href="#" onclick="console.log('The link was clicked.'); return false"> Click me </a>在 React 中,可能是這樣的:
class ClickTop extends React.Component{ constructor(props) { super(props) this.state = {context:0} } add = (e) => { e.preventDefault(); this.setState({ context: ++this.state.context }) } render(){ return ( <div> <button onClick={this.add}>點擊累加</button> <p>{this.state.context}</p> </div> ) } }
在這里,e 是一個合成事件,React 根據 W3C 規范來定義這些合成事件,所以你不需要擔心跨瀏覽器的兼容性問題,
使用 React 時,你一般不需要使用 addEventListener 為已創建的 DOM 元素添加監聽器,事實上,你只需要在該元素初始渲染的時候添加監聽器即可,
當你使用 ES6 class 語法定義一個組件的時候,通常的做法是將事件處理函式宣告為 class 中的方法,
class ClickTop extends React.Component{
constructor(props) {
super(props)
this.state = {context:0}
this.add = this.add.bind(this)
}
add(e){
this.setState({
context: ++this.state.context
})
}
render(){
return (
<div>
<button onClick={this.add}>點擊累加</button>
<p>{this.state.context}</p>
</div>
)
}
}
你必須謹慎對待 JSX 回呼函式中的 this,在 JavaScript 中,class 的方法默認不會系結 this,如果你忘記系結 this.this 并把它傳入了 onClick,當你呼叫這個函式的時候 this 的值為 undefined,
這并不是 React 特有的行為;這其實與 JavaScript 函式作業原理有關,通常情況下,如果你沒有在方法后面添加 (),例如 onClick={this.this},你應該為這個方法系結 this,
如果覺得使用 bind 很麻煩,這里有兩種方式可以解決,如果你正在使用實驗性的 public class fields 語法,你可以使用 class fields 正確的系結回呼函式:
class ClickTop extends React.Component{
constructor(props) {
super(props)
this.state = {context:0}
}
add = () =>{
this.setState({
context: ++this.state.context
})
}
render(){
return (
<div>
<button onClick={this.add}>點擊累加</button>
<p>{this.state.context}</p>
</div>
)
}
}
如果你沒有使用 class fields 語法,你可以在回呼中使用箭頭函式:
class ClickTop extends React.Component{
constructor(props) {
super(props)
this.state = {context:0}
}
add(){
this.setState({
context: ++this.state.context
})
}
render(){
return (
<div>
<button onClick={() => this.add()}>點擊累加</button>
<p>{this.state.context}</p>
</div>
)
}
}
此語法問題在于每次渲染 LoggingButton 時都會創建不同的回呼函式,在大多數情況下,這沒什么問題,但如果該回呼函式作為 prop 傳入子組件時,這些組件可能會進行額外的重新渲染,我們通常建議在構造器中系結或使用 class fields 語法來避免這類性能問題,
1 向事件處理程式傳遞引數
在回圈中,通常我們會為事件處理函式傳遞額外的引數,例如,若 id 是你要洗掉那一行的 ID,
class ClickTop extends React.Component{
constructor(props) {
super(props)
this.state = {context:0}
}
add(data){
this.setState({
context: ++data
})
}
render(){
return (
<div>
<button onClick={this.add.bind(this, this.state.context)}>點擊累加</button>
<p>{this.state.context}</p>
</div>
)
}
}
// 示例2
class ClickTop extends React.Component{
constructor(props) {
super(props)
this.state = {context:0}
}
add(data){
this.setState({
context: ++data
})
}
render(){
return (
<div>
<button onClick={e => this.add(this.state.context)}>點擊累加</button>
<p>{this.state.context}</p>
</div>
)
}
}
上述兩種方式是等價的,分別通過箭頭函式和 Function.prototype.bind 來實作,
在這兩種情況下,React 的事件物件 e 會被作為第二個引數傳遞,如果通過箭頭函式的方式,事件物件必須顯式的進行傳遞,而通過 bind 的方式,事件物件以及更多的引數將會被隱式的進行傳遞,
6 條件渲染
在 React 中,你可以創建不同的組件來封裝各種你需要的行為,然后,依據應用的不同狀態,你可以只渲染對應狀態下的部分內容,
React 中的條件渲染和 JavaScript 中的一樣,使用 JavaScript 運算子 if 或者條件運算子去創建元素來表現當前的狀態,然后讓 React 根據它們來更新 UI,
render() {
let jsxObj = null
if (this.props.type){
jsxObj = <Times time={this.state.data}></Times>
}else {
jsxObj = <ClickTop></ClickTop>
}
return (
<div>
<p>當前的時間是</p>
<p>{this.state.data.toLocaleTimeString()}</p>
{jsxObj}
</div>
)
}
1 元素變數
你可以使用變數來儲存元素, 它可以幫助你有條件地渲染組件的一部分,而其他的渲染部分并不會因此而改變,
class Text extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>顯示</div>
)
}
}
class Text1 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>隱藏</div>
)
}
}
class Time extends React.Component {
constructor(props) {
super(props);
this.state = {type: true}
}
typeState(type) {
this.setState({
type: type
})
}
render() {
let strObj = null;
if (this.state.type){
strObj = <Text></Text>
}else {
strObj = <Text1></Text1>
}
return (
<div>
<button onClick={this.typeState.bind(this,true)}>顯示</button>
<button onClick={this.typeState.bind(this,false)}>隱藏</button>
{strObj}
</div>
)
}
}
ReactDOM.render(
<Time></Time>,
document.getElementById('example')
)
宣告一個變數并使用 if 陳述句進行條件渲染是不錯的方式,但有時你可能會想使用更為簡潔的語法,
2 與運算子 &&
3 三目運算子
另一種行內條件渲染的方法是使用 JavaScript 中的三目運算子 condition ? true : false,
class Text extends React.Component {
constructor(props) {
super(props);
this.state = {text: false}
}
render() {
return (
<div>{this.state.text ? '顯示' : '還是顯示'}</div>
)
}
}
同樣的,它也可以用于較為復雜的運算式中,雖然看起來不是很直觀:
class Text extends React.Component {
constructor(props) {
super(props);
this.state = {text: false}
}
render() {
return (
<div>{this.state.text ? '顯示' : '還是顯示'}</div>
)
}
}
class Text1 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>隱藏</div>
)
}
}
class Time extends React.Component {
constructor(props) {
super(props);
this.state = {type: true}
}
typeState(type) {
this.setState({
type: type
})
}
render() {
return (
<div>
<button onClick={this.typeState.bind(this,true)}>顯示</button>
<button onClick={this.typeState.bind(this,false)}>隱藏</button>
{this.state.type ? <Text></Text> : <Text1></Text1>}
</div>
)
}
}
ReactDOM.render(
<Time></Time>,
document.getElementById('example')
)
就像在 JavaScript 中一樣,你可以根據團隊的習慣來選擇可讀性更高的代碼風格,需要注意的是,如果條件變得過于復雜,那你應該考慮如何提取組件,
4 阻止組件渲染
在極少數情況下,你可能希望能隱藏組件,即使它已經被其他組件渲染,若要完成此操作,你可以讓 render 方法直接回傳 null,而不進行任何渲染,
class Text extends React.Component {
constructor(props) {
super(props);
this.state = {text: false}
}
render() {
if (!this.state.text){
return null
}
return (
<div>{this.state.text ? '顯示' : '還是顯示'}</div>
)
}
}
在組件的 render 方法中回傳 null 并不會影響組件的生命周期,
7 串列 & Key
Javascript 中如何轉化串列,
如下代碼,我們使用 map() 函式讓陣列中的每一項變雙倍,然后我們得到了一個新的串列 doubled 并列印出來:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
代碼列印出 [2, 4, 6, 8, 10],
在 React 中,把陣列轉化為元素串列的程序是相似的
1 渲染多個組件
你可以通過使用 {} 在 JSX 內構建一個元素集合,
用 Javascript 中的 map() 方法來遍歷 numbers 陣列,將陣列中的每個元素變成
- 標簽,最后將得到的陣列賦值給 listItems:
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> );把整個 listItems 插入到
- 元素中,然后渲染進 DOM
ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );這段代碼生成了一個 1 到 5 的專案符號串列,
2 基礎串列組件
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map(element => <li>{element}</li>); return ( <ul>{arrList}</ul> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )當我們運行這段代碼,將會看到一個警告 a key should be provided for list items,意思是當你創建一個元素時,必須包括一個特殊的 key 屬性,給每個串列元素分配一個 key 屬性來解決上面的那個警告:
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map(element => <li key={element.toString()}>{element}</li>); return ( <ul>{arrList}</ul> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )3 key
key 幫助 React 識別哪些元素改變了,比如被添加或洗掉,因此你應當給陣列中的每一個元素賦予一個確定的標識,
一個元素的 key 最好是這個元素在串列中擁有的一個獨一無二的字串,通常,我們使用資料中的 id 來作為元素的 key:const todoItems = todos.map((todo) => <li key={todo.id}> {todo.text} </li> );當元素沒有確定 id 的時候,萬不得已你可以使用元素索引 index 作為 key:
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map((element, index) => <li key={index.toString()}>{element}</li>); return ( <ul>{arrList}</ul> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )如果串列專案的順序可能會變化,我們不建議使用索引來用作 key 值,因為這樣做會導致性能變差,還可能引起組件狀態的問題,
4 用 key 提取組件
元素的 key 只有放在就近的陣列背景關系中才有意義,
比方說,如果你提取出一個 ListItem 組件,你應該把 key 保留在陣列中的這個 元素上,而不是放在 ListItem 組件中的 - 元素上,
-
不正確的寫法
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map((element, index) => <Str1></Str1>); return ( <div>{arrList}</div> ) } } class Str1 extends React.Component{ constructor() { super(); } render(){ return ( <div key={index.toString()}>我正在學習react</div> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') ) -
正確的寫法
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map((element, index) => <Str1 key={index.toString()}></Str1>); return ( <div>{arrList}</div> ) } } class Str1 extends React.Component{ constructor() { super(); } render(){ return ( <div>我正在學習react</div> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )一個好的經驗法則是:在 map() 方法中的元素需要設定 key 屬性,
5 key 只是在兄弟節點之間必須唯一
陣列元素中使用的 key 在其兄弟節點之間應該是獨一無二的,然而,它們不需要是全域唯一的,當我們生成兩個不同的陣列時,我們可以使用相同的 key 值:
class Str extends React.Component{ constructor(props) { super(props); } render(){ let arrList = this.props.data.map((element, index) => <Str1 key={index.toString()}></Str1>); let arrList1 = this.props.data.map((element, index) => <Str2 key={index.toString()}></Str2>); return ( <div> {arrList} {arrList1} </div> ) } } class Str2 extends React.Component{ constructor() { super(); } render(){ return ( <div>我正在學習java</div> ) } } class Str1 extends React.Component{ constructor() { super(); } render(){ return ( <div>我正在學習react</div> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )key 會傳遞資訊給 React ,但不會傳遞給你的組件,如果你的組件中需要使用 key 屬性的值,請用其他屬性名顯式傳遞這個值:
const content = posts.map((post) => <Post key={post.id} id={post.id} title={post.title} /> );上面例子中,Post 組件可以讀出 props.id,但是不能讀出 props.key,
6 在 JSX 中嵌入 map()
JSX 允許在大括號中嵌入任何運算式,所以我們可以行內 map() 回傳的結果:
class Str extends React.Component{ constructor(props) { super(props); } render(){ return ( <div> { this.props.data.map((element, index) => <Str2 key={index.toString()}></Str2>) } </div> ) } } class Str2 extends React.Component{ constructor() { super(); } render(){ return ( <div>我正在學習java</div> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str data={arr}></Str>, document.getElementById('example') )這么做有時可以使你的代碼更清晰,但有時這種風格也會被濫用,就像在 JavaScript 中一樣,何時需要為了可讀性提取出一個變數,這完全取決于你,但請記住,如果一個 map() 嵌套了太多層級,那可能就是你提取組件的一個好時機,
8 表單
在 React 里,HTML 表單元素的作業方式和其他的 DOM 元素有些不同,這是因為表單元素通常會保持一些內部的 state,例如這個純 HTML 表單只接受一個名稱:
<form> <label> 名字: <input type="text" name="name" /> </label> <input type="submit" value="提交" /> </form>此表單具有默認的 HTML 表單行為,即在用戶提交表單后瀏覽到新頁面,如果你在 React 中執行相同的代碼,它依然有效,但大多數情況下,使用 JavaScript 函式可以很方便的處理表單的提交, 同時還可以訪問用戶填寫的表單資料,實作這種效果的標準方式是使用“受控組件”,
1 受控組件
在 HTML 中,表單元素(如、 和 )通常自己維護 state,并根據用戶輸入進行更新,而在 React 中,可變狀態(mutable state)通常保存在組件的 state 屬性中,并且只能通過使用 setState()來更新,
可以把兩者結合起來,使 React 的 state 成為“唯一資料源”,渲染表單的 React 組件還控制著用戶輸入程序中表單發生的操作,被 React 以這種方式控制取值的表單輸入元素就叫做“受控組件”,class Str2 extends React.Component{ constructor() { super(); this.state = {value:"這是賬號"} } on_change(e){ this.setState({ value:e.target.value }) } commit(e){ console.log(this.state.value) e.preventDefault() } render(){ return ( <form onSubmit={this.commit.bind(this)}> <label> <input value={this.state.value} onChange={this.on_change.bind(this)} /> </label> <input type="submit" value="提交" /> </form> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str2></Str2>, document.getElementById('example') )由于在表單元素上設定了 value 屬性,因此顯示的值將始終為 this.state.value,這使得 React 的 state 成為唯一資料源,由于 on_change 在每次按鍵時都會執行并更新 React 的 state,因此顯示的值將隨著用戶輸入而更新,
對于受控組件來說,輸入的值始終由 React 的 state 驅動,你也可以將 value 傳遞給其他 UI 元素,或者通過其他事件處理函式重置,但這意味著你需要撰寫更多的代碼,2 textarea 標簽
在 HTML 中, 元素通過其子元素定義其文本:
<textarea> 你好, 這是在 text area 里的文本 </textarea>而在 React 中, 使用 value 屬性代替,這樣,可以使得使用 的表單和使用單行 input 的表單非常類似:
class Str2 extends React.Component{ constructor() { super(); this.state = {value:"這是賬號"} } on_change(e){ this.setState({ value:e.target.value }) } commit(e){ console.log(this.state.value) e.preventDefault() } render(){ return ( <form onSubmit={this.commit.bind(this)}> <label> <textarea value={this.state.value} onChange={this.on_change.bind(this)} /> </label> <input type="submit" value="提交" /> </form> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str2></Str2>, document.getElementById('example') )3 select 標簽
在 HTML 中, 創建下拉串列標簽,例如,如下 HTML 創建了水果相關的下拉串列:
<select> <option value="grapefruit">葡萄柚</option> <option value="lime">酸橙</option> <option selected value="coconut">椰子</option> <option value="mango">芒果</option> </select>請注意,由于 selected 屬性的緣故,java選項默認被選中,React 并不會使用 selected 屬性,而是在根 select 標簽上使用 value 屬性,這在受控組件中更便捷,因為您只需要在根標簽中更新它,
class Str2 extends React.Component { constructor() { super(); this.state = {value: "java"} } on_change(e) { this.setState({ value: e.target.value }) } commit(e) { console.log(this.state.value) e.preventDefault() } render() { return ( <form onSubmit={this.commit.bind(this)}> <label> <select value={this.state.value} onChange={this.on_change.bind(this)}> <option value="js">js</option> <option value="python">python</option> <option value="java">java</option> <option value="c">c</option> </select> </label> <input type="submit" value="提交"/> </form> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str2></Str2>, document.getElementById('example') )總的來說,這使得 , 和 之類的標簽都非常相似—它們都接受一個 value 屬性,你可以使用它來實作受控組件,
注意
你可以將陣列傳遞到 value 屬性中,以支持在 select 標簽中選擇多個選項:<select multiple={true} value={['B', 'C']}> // 示例 class Str2 extends React.Component { constructor() { super(); this.state = {value: ['js', 'java']} } on_change(e) { this.setState({ value: e.target.value }) } commit(e) { console.log(this.state.value) e.preventDefault() } render() { return ( <form onSubmit={this.commit.bind(this)}> <label> <select multiple={true} value={this.state.value} onChange={this.on_change.bind(this)}> <option value="js">js</option> <option value="python">python</option> <option value="java">java</option> <option value="c">c</option> </select> </label> <input type="submit" value="提交"/> </form> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str2></Str2>, document.getElementById('example') )4 處理多個輸入
當需要處理多個 input 元素時,我們可以給每個元素添加 name 屬性,并讓處理函式根據 event.target.name 的值選擇要執行的操作,
class Str2 extends React.Component { constructor() { super(); this.state = {isGoing: '' , numberOfGuests:''} } on_change(e) { let event = e.target console.log(event) let value = event.type === 'checkbox' ? event.checked : event.value let name = event.name this.setState({ [name] : value }) } commit(e) { console.log(this.state) e.preventDefault() } render() { return ( <form onSubmit={this.commit.bind(this)}> <label> <input type="checkbox" name="isGoing" onChange={this.on_change.bind(this)}/> </label> <label> <input type="number" name="numberOfGuests" onChange={this.on_change.bind(this)}/> </label> <input type="submit" value="提交"/> </form> ) } } let arr = [1, 2, 3, 4, 5, 6, 7] ReactDOM.render( <Str2></Str2>, document.getElementById('example') )9 狀態提升
通常,多個組件需要反映相同的變化資料,這時我們建議將共享狀態提升到最近的共同父組件中去,
創建一個用于計算水在給定溫度下是否會沸騰的溫度計算器,從一個名為 BoilingVerdict 的組件開始,它接受 celsius 溫度作為一個 prop,并據此列印出該溫度是否足以將水煮沸的結果,class BoilingVerdict extends React.Component{ constructor(props) { super(props); } render(){ if (this.props.celsius >= 100){ return <p>水開了</p> } return <p>水沒開</p> } } ReactDOM.render( <Str2></Str2>, document.getElementById('example') )創建一個名為 Calculator 的組件,它渲染一個用于輸入溫度的 ,并將其值保存在 this.state.temperature 中,
1 添加第二個輸入框
class BoilingVerdict extends React.Component { constructor(props) { super(props); } render() { if (this.props.celsius >= 100) { return <p>水開了</p> } return <p>水沒開</p> } } class TemperatureInput extends React.Component { constructor(props) { super(props); this.state = {temperature: ""} } on_change(e) { this.setState({ temperature: e.target.value }) } render() { return ( <div> <form> <label> 當前輸入的是{this.props.scale} <input onChange={this.on_change.bind(this)} value={this.state.temperature}/> </label> </form> <BoilingVerdict celsius={parseFloat(this.state.temperature)}></BoilingVerdict> </div> ) } } class Calculator extends React.Component { constructor() { super(); } render() { return ( <div> <TemperatureInput scale="攝氏度"></TemperatureInput> <TemperatureInput scale="華氏度"></TemperatureInput> </div> ) } } ReactDOM.render( <Calculator></Calculator>, document.getElementById('example') )現在有了兩個輸入框,但當你在其中一個輸入溫度時,另一個并不會更新,這與我們的要求相矛盾:我們希望讓它們保持同步,
另外,我們也不能通過 Calculator 組件展示 BoilingVerdict 組件的渲染結果,因為 Calculator 組件并不知道隱藏在 TemperatureInput 組件中的當前溫度是多少, - 元素中,然后渲染進 DOM
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/295422.html
標籤:其他
上一篇:“空物件 {}” 與 “空陣列 []” 的相加問題(詳解!!!)
下一篇:JSON資料格式
