React系統性學習(下)
$ 前言
在中我們主要學習了
什麼是React
JSX語法
元素渲染
元件(
Component
) 和 屬性 (Props
)狀態(
State
) 和 生命週期(lifeCircle
)處理事件
條件渲染
本文我們將繼續學習
列表(
List
) 和 鍵(keys
)表單(
Forms
)狀態提升(
Lifting State Up
)組合 VS 繼承 (
Composition vs inheritance
)
$ 版本宣告
本文使用版本 React v16.2.0
$ 列表 和 鍵
列表(List
), 鍵(Key
)
回顧一下在javascript
中如何轉換列表:在陣列中使用map()
函式對numbers
陣列中的每個元素依次執操作
const numbers = [1, 2, 3, 4, 5];const doubled = numbers.map((number) => number * 2);console.log(doubled) // 2, 4, 6, 8, 10
React 基本借鑑了以上寫法,只不過將陣列替換成了元素列表
多元件渲染
可以建立元素集合,並用一對大括號 {}
在 JSX 中直接將其引用即可
下面,我們用 JavaScript 的 map()
函式將 numbers
陣列迴圈處理。對於每一項,我們返回一個 元素。最終,我們將結果元素陣列分配給
listItems
:
const numbers = [1, 2, 3, 4, 5];const listItems = numbers.map((number) =>
再把整個 listItems
陣列包含到一個
- 元素,並渲染到DOM
ReactDOM.render(
- {listItems}
基本列表元件
通常情況下,我們會在一個元件中渲染列表而不是直接放到root
上。重構一下上例
function NumberList(props) { const numbers = props.number; const listItems = numbers.map((number) =>
- {listItems}
當執行上述程式碼的時候,將會受到一個警告:a key should be provided for list items
,要求應該為元素提供一個鍵(注:min版本react無提示)。要去掉這個警告也簡單,只需要在listItem
的每個li
中增加key
屬性即可,增加後的每個如下
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) =>
- {listItems}
當建立元素列表時,“key” 是一個你需要包含的特殊字串屬性,那為什麼要包含呢?
鍵(Keys)
鍵Keys
幫助React標識那個項被修改、新增或者移除了。陣列中的每一個元素都應該有一個唯一不變的鍵來標識。
挑選key
最好的辦法是使用一個在它的同輩元素中不重複的表示字串。多數情況下可以使用資料中的IDs
來作為Keys
。但是還是會遇到沒有id
欄位的資料,這種情況你可以使用資料項的索引值
cosnt todoItems = todos.map((todo, index) => // 資料項沒有IDs時使用該辦法
如果列表項可能被重新排序,這種用法存在一定的效能問題,React會產生時間複雜度為O(n^3)的演算法執行。因此優先使用資料項本身的欄位內容來設定鍵
使用 Keys 提取元件
Keys
只有在陣列的上下文中存在意義。例如,如果你提取了一個ListItem
元件,應該把key
放置在陣列處理的
元素中,而不能放在ListItem
元件自身的根元素上。
以下的用法就是錯誤的
function ListItem(props) { const value = props.value; return ( // 錯誤!不需要再這裡指定 key
- {listItems}
應該寫成如下
function ListItem(props) { // 正確!這裡不需要指定 key : return
-
{listItems}
keys 在同輩元素中必須唯一
在陣列中使用的 keys 必須在它們的同輩之間唯一。然而它們並不需要全域性唯一。我們可以在操作兩個不同陣列的時候使用相同的 keys :
function Blog(props) { const sidebar = (
-
{props.posts.map((post) =>
- {post.title} )}
{post.title}
{post.content}
{content}
【注意】鍵是一個內部對映,他不會作為props
傳遞給元件內部,如果你需要在元件中使用到這個值,可以自定義一個屬性名將該值傳入到props
中,如下例中我們定義了一個id
屬性傳入給props
.
const content = posts.map((post) =>);
在這個例子中,我們能讀取props.id
,但是讀取不了props.key
直接在JSX中使用map()
在上例中我們先宣告瞭一個listItem
然後在jsx中引用,然而我們也能在JSX
中直接引用,稱之為 內聯map()
function NumberList(props) { const numbers = props.numbers; return (
-
{ numbers.map((number) =>
至於選用哪種風格編寫,只要遵循程式碼清晰易讀原則即可
$ 表單
&esmp; HTML 表單元素與 React 中的其他 DOM 元素有所不同,因為表單元素自然地保留了一些內部狀態。例如,這個純 HTML 表單接受一個單獨的 name:
該表單和 HTML 表單的預設行為一致,當使用者提交此表單時瀏覽器會開啟一個新頁面。如果你希望 React 中保持這個行為,也可以工作。但是多數情況下,用一個處理表單提交併訪問使用者輸入到表單中的資料的 JavaScript 函式也很方便。實現這一點的標準方法是使用一種稱為“受控元件(controlled components
)”的技術。
受控元件(Controlled Components)
在 HTML 中,表單元素如 ,
和
表單元素通常保持自己的狀態,並根據使用者輸入進行更新。而在 React 中,可變狀態一般儲存在元件的 state(狀態) 屬性中,並且只能透過
setState()
更新。
透過使 React 的 state 成為 “單一資料來源原則” 來結合這兩個形式。然後渲染表單的 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 () } }
設定表單元素的value
屬性之後,其顯示值將由this.state.value
決定,以滿足react
狀態的同一個資料理念。每次鍵盤敲擊之後會執行handleChange
方法以便更新React狀態,顯示只也將隨著使用者的輸入而改變。
由於value
屬性設定在我們的表單元素上,顯示的值總是this.state.value
,以滿足state
狀態的同意資料理念。由於 handleChange
在每次敲擊鍵盤時執行,以更新React state
,顯示的值將更新為使用者的輸入
對於受控元件來說,每一次state
的變化都會伴有相關聯的處理函式。這使得可以直接修改或驗證使用者的輸入。比如,我們希望強制name
的輸入都是大寫字母,可以如下實現
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); }
textarea標籤
在 HTML 中, 元素透過它的子節點定義了它的文字值:
在 React 中, 的賦值使用
value
屬性替代。這樣一來,表單中 的書寫方式接近於單行文字輸入框 :
class EssayForm extends React.Component { constructor(props) { super(props); this.state = { value: 'Please write an esay about your favorite DOM element.' } } // ... render() { return () } }
注意,this.state.value
在建構函式中初始化,所以這些文字一開始就出現在文字域中。
select 標籤
在 HTML 中, 建立了一個下拉選單用法如下
html利用selected
預設選中,但在React中,不使用selected
,而是給
標籤中增加一個value
屬性,這使得受控元件使用更加方便,因為你只需要更新一處變數即可。
class FlavorForm extends React.Component { // ... render() { return (); } }
總的來說,這使 ,
和
都以類似的方式工作 —— 它們都接受一個 value
屬性可以用來實現一個受控元件。
多選select
使用多選select
時,需要給select
標籤增加value
屬性,同時給value
屬性賦值一個陣列
# 利用e.target
合併多個輸入元素的處理事件
當您需要處理多個受控的 input 元素時,您可以為每個元素新增一個 name 屬性,並且讓處理函式根據 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 (); } } ReactDOM.render(
注意這裡使用ES6語法來更新與給定輸入名稱相對應的 state
(狀態) 鍵的辦法
this.setState({ [name]: value })
var partialState = {}; partialState[name] = value;this.setState(partialState);
由於 setState()
自動,所以我們只需要呼叫更改的部分即可。
受控 Input 元件的 null 值
在 受控元件上指定值 prop 可防止使用者更改輸入,除非您希望如此。 如果你已經指定了一個 value
,但是輸入仍然是可編輯的,你可能會意外地把 value
設定為undefined
或 null
。
以下程式碼演示了這一點。 (輸入首先被鎖定,但在短暫的延遲後可以編輯。)
ReactDOM.render(, mountNode); setTimeout(function() { ReactDOM.render(, mountNode); }, 1000);
$ 狀態提升 (Lifting State Up)
通常情況下,同一個資料的變化需要幾個不同的元件來反映。我們建議提升共享的狀態到它們最近的祖先元件中。為了更好的理解,從一個案例來分析
溫度計算器
在本案例中,我們採用自下而上的方式來建立一個溫度計算器,用來計算在一個給定溫度下水是否會沸騰(水溫是否高於100C)
(1)建立一個 BoilingVerdict
元件,用來判水是否會沸騰並列印
function Bioling Verdict(props) { if (props.celsius >= 100) { returnThe water would boil.
} returnThe water would not boil.
}
(2)有了判斷溫度的元件之後,我們需要一個Calculator
元件,他需要包含一個提供我們輸入文圖,並在
this.state.temperature
中儲存值。另外,以上BoilingVerdict
元件將會獲取到該輸入值並進行判斷
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 () } }
(3)現在我們實現了基礎的父子元件通訊功能,假設我們有這樣的需求:除了一個設施文圖的輸入之外,還需要有一個華氏溫度輸入,並且要求兩者保持自動同步
我們從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 () } }
抽出TemperatureInput
之後,Calculator
元件如下
class Calculator extends React.Component { render() { return (); } }
現在有了兩個輸入框,但這兩個元件是獨立存在的,不會互相影響,也就是說,輸入其中一個溫度另一個並不會改變,與需求不符
我們不能再Calculator
中顯示BoilingVerdict
, Calcultor
不知道當前的溫度,因為它是在TemperatureInput
中隱藏的, 因此我們需要編寫轉換函式
(4)編寫轉換函式
我們先來實現兩個函式在攝氏度和華氏度之間轉換
function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } functin toFahrenheit(celsius0 { return (celsius * 9 / 5) + 32; }
接下來,編寫函式用來接收一個字串temperature
和一個轉化器函式作為引數,並返回一個字串,這個函式在兩個輸入之間進行相互轉換。為了健壯性,對於無效的temperature
值,返回一個空字串,輸出保留三位小數
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
狀態提升
目前,兩個 TempetureInput 元件都將其值保留在本地狀態中,但是我們希望這兩個輸入時相互同步的。但我們更新攝氏溫度輸入時,華氏溫度輸入應該反映並自動更新,反之亦然。
在React 中,共享state(狀態)是透過將其移動到需要的的元件的最接近的共同祖先元件來實現的,這被稱之為狀態提升(Lifting State Up)。我們將從TemperatureInput
中移除相關狀態本地狀態,並將其移動到Calculator
中
如果Calculator
擁有共享狀態,那麼他將成為兩個輸入當前溫度的單一資料來源
。他可以指示他們具有彼此一致的值 。由於兩個TemperatureInput
的元件的props
來自於同一個父級Calculator
元件,連個輸入將始終保持同步
讓我們來一步步實現這個過程
(1)將值挪出元件,用props
傳入
render() { // const temperature = this.state.temperature; const temperature = this.props.temperature; }
我們知道,props
是隻讀的,因此我們不能根據子元件呼叫this.setState()
來改變它。這個問題,在React中通常使用 受控的方式來解決。就像DOM一樣接收一個
value
和onChange
prop
, 所以可以定製Temperature
接受來自其腹肌 Calculator
的 temperature
和 onTemperatureChange
:
thandleChange(e) { // 之前是:this.setState({ temperature: e.target.value }); this.props.onTemperatureChange(e.target.value); }
請注意,之定義元件中的
templature
或onTemperatureChange
prop
(屬性)名稱沒有特殊的含義。我們可以命名為任何其他名字,就像命名他們為value
和onChange
。是一個和常見的慣例
onTemperatureChange prop
和 temperature prop
一起由父級的Calculator
元件提供,他將透過修改自己的本地state
來處理資料變更,從而透過新值重新渲染兩個輸入。現在我們的程式碼如下
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 ) } }
我們將當前輸入的 temperature
和scale
儲存在本地的state
中,這是我們衝輸入“提升”的state(狀態)
,他將作為連個輸入的“單一資料來源”。為了渲染這兩個輸入,我們需要知道的所有資料的最小表示,如攝氏度輸入37,這時Calculator
元件狀態將是:
{ temperature: '37', scale: 'c'}
我們確實可以儲存兩個輸入框(攝氏度和華氏度)的值,但事實證明是不必要的。我們只要儲存最近更改的輸入框的值,以及他們所表示的度量衡(scale
)就足夠了。然後推斷出另一個值。這也是我們實現兩個輸入框保持同步的途徑
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 () } }
現在,無論你編輯哪個輸入框,Calculator
中的 this.state.temperature
和 this.state.scale
都會更新。其中一個輸入框獲取值,所以任何使用者輸入都被保留,並且另一個輸入總是基於它重新計算值。
讓我們回顧一下編輯輸入時會發生什麼:
React 呼叫在 DOM
上的
onChange
指定的函式。在我們的例子中,這是TemperatureInput
元件中的handleChange
方法。TemperatureInput
元件中的handleChange
方法使用 新的期望值 呼叫this.props.onTemperatureChange()
。TemperatureInput
元件中的props(屬性)
,包括onTemperatureChange
,由其父元件Calculator
提供。當它預先呈現時,
Calculator
指定了攝氏TemperatureInput
的onTemperatureChange
是Calculator
的handleCelsiusChange
方法,並且華氏TemperatureInput
的onTemperatureChange
是Calculator
的handleFahrenheitChange
方法。因此,會根據我們編輯的輸入框,分別呼叫這兩個Calculator
方法。在這些方法中,
Calculator
元件要求 React 透過使用 新的輸入值 和 剛剛編輯的輸入框的當前度量衡 來呼叫this.setState()
來重新渲染自身React 呼叫
Calculator
元件的render
方法來了解 UI 外觀應該是什麼樣子。基於當前溫度和啟用的度量衡來重新計算兩個輸入框的值。這裡進行溫度轉換React 使用
Calculator
指定的新props
(屬性) 呼叫各個TemperatureInput
元件的render
方法。 它瞭解 UI 外觀應該是什麼樣子React DOM 更新 DOM 以匹配期望的輸入值。我們剛剛編輯的輸入框接收當前值,另一個輸入框更新為轉換後的溫度。
^狀態提升經驗總結
在一個 React 應用中,對於任何可變的資料都應該遵循“單一資料來源”原則,通常情況下,state
首先被新增到需要它進行渲染的元件,然後如果其他的元件也需要它,你可以提升狀態到他們最近的祖先元件。你應該依賴從上到下的資料流向,而不是試圖在不同的元件中同步狀態。
提升狀態相對於雙向繫結方法需要寫更多的"模板"程式碼,但是有個好處,他可以更方便的找到和隔離bugs
。由於熱河state
(狀態)都"存活"若干個元件中,而且可以分別對其獨立修改,所以發生錯誤的可能性大大減少。另外,你可以實現任何定製的邏輯來拒絕或者轉換使用者輸入。
如果某個東西可以從props(屬性)
或者state(狀態)
得到,那麼他可能不應該在state
中。例如我們只儲存最後編輯的temperature
和scale
,而不是儲存celsiusValue
和fahrenheitValue
。另一個輸入框的值總是在render()
中計算得到。這是我們對其進行清除和四捨五入到其他欄位的同事不會丟失其精度
當你看到UI中的錯誤,你可以使用React開發者工具來檢查props
,並向上遍歷樹,知道找到負責更新狀態的元件,這是你可以跟蹤到bug
的源頭:Monitoring State in React DevTools
$ 組合 VS 繼承
組合 Composition
vs 繼承Inheritance
React 擁有一個強大的組合模型,建議使用組合而不是繼承以實現程式碼的重用
接下來同樣從案例觸發來考慮幾個問題,新手一般會用繼承,然後這裡推薦使用組合
包含
一些元件在設計前無法或者自己要使用什麼子元件,尤其是在 Sidebar
和 Dialog
等通用的 “容器” 中比較常見
這種元件建議使用特殊的children prop
來直接傳遞子元素到他們的輸出中:
function FancyBorder(props) { return (// children 表示來自父元件中的子元素 {props.children}) }
這允許其他元件透過巢狀JSX傳遞任意子元件給他們,比如在父元件中有h1
和p
子元素
function WelcomeDialog() { return (Welcome
Thank you for your visitiong
) }
在 這是一種簡單的用法,這種案例並不常見,有時候我們需要在一個元件中有多個“佔位符”,這種情況下,你可以使用自定義的 如 有時候,我們考慮元件作為其它元件的“特殊情況”。例如,我們可能說一個 {props.message} 這對於類定義的元件組合也同樣適用
{props.message} 在 Facebook ,在千萬的元件中使用 React,我們還沒有發現任何用例,值得我們建議你用繼承層次結構來建立元件。 使用 如果要在元件之間重用非 U I功能,我們建議將其提取到單獨的 JavaScript 模組中。元件可以匯入它並使用該函式,物件或類,而不擴充套件它。 React的顛覆性思想不同於之前的任何一個框架,掌握React這門技術,會幫助你自己思考如何更高效能、高效率的程式設計,這可能影響你方方面面和以後的任意一次程式設計經歷。 本文中如有錯誤之處,歡迎指正。
JSX 標籤中的任何內容被傳遞到FancyBorder
元件中,作為一個 children prop(屬性)
。由於 FancyBorder
渲染{props.children}
到一個 prop
屬性,而不是children
:function Contacts() { return ;
}function Chat() { return ;
}function SplitPane(props) { return (
和
等 React
元素本質上也是物件,所以可以將其像其他資料一樣作為 props(屬性) 傳遞使用。特例
WelcomeDialog
是 Dialog
的一個特殊用例。
在React中,也可以使用組合來實現,一個偏“特殊”的元件渲染出一個偏“通用”的元件,透過 props(屬性)
配置它:function FancyBorder(props) { return (
{props.title}
function FancyBorder(props) { return (
{props.title}
如何看待繼承?
props(屬性)
和 組合已經足夠靈活來明確、安全的定製一個元件的外觀和行為。切記,元件可以接受任意的 props(屬性)
,包括原始值、React 元素,或者函式
$ 後語
作者:果汁涼茶丶
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3016/viewspace-2813624/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 彈性分散式深度學習系統分散式深度學習
- 初步學習密碼系統的安全性密碼
- 大資料Hadoop系統性學習路線圖大資料Hadoop
- React學習React
- 學習ReactReact
- ElasticDL: Kubernetes-native 彈性分散式深度學習系統AST分散式深度學習
- React學習手記2-屬性校驗和預設屬性React
- 機器學習:線性迴歸(下)機器學習
- 個人學習性文章統計
- 學習react教程React
- react + typescript 學習ReactTypeScript
- 如何系統學習C 語言(下)之 檔案篇
- 系統學習NLP(十)--詞性標註演算法綜述詞性標註演算法
- MySQL如何系統學習MySql
- 作業系統學習作業系統
- React學習(1)-create-react-appReactAPP
- react js學習手記:react 事件ReactJS事件
- React 學習資源React
- react 學習筆記React筆記
- React學習之HookReactHook
- React 學習之 createElementReact
- react學習筆記React筆記
- react學習目錄React
- React簡明學習React
- react 學習 問題React
- 推薦系統遇上深度學習(二十一)--階段性回顧深度學習
- 淺談個性化推薦系統中的非取樣學習
- SpringBoot系統學習 - 配置篇Spring Boot
- Dart基礎系統學習Dart
- 系統學習NLP(十六)--DSSMSSM
- Linux系統基礎學習Linux
- MySQL學習之日誌系統MySql
- 工作學習中如何選擇作業系統?如何學習linux作業系統作業系統Linux
- 如何系統學習C 語言(下)之 預處理命令篇
- 學習ASP.NET Core(10)-全域性日誌與xUnit系統測試ASP.NET
- React 學習筆記【三】React筆記
- React 學習筆記【一】React筆記
- React 學習筆記【二】React筆記