函陣列件是一種簡單的定義元件的方式,通過一個JavaScript函數來定義元件。函數接收一個props物件作為引數,並返回一個React元素作為輸出。
1 <!-- 準備好一個「容器」 -->
2 <div id="test"></div>
3
4 <!-- 引入react核心庫 -->
5 <script type="text/javascript" src="../js/react.development.js"></script>
6 <!-- 引入react-dom,用於支援react操作DOM -->
7 <script type="text/javascript" src="../js/react-dom.development.js"></script>
8 <!-- 引入babel,用於將jsx轉為js -->
9 <script type="text/javascript" src="../js/babel.min.js"></script>
10
11 <script type="text/babel">
12 //1.建立函數式元件
13 function MyComponent(){
14 console.log(this); //此處的this是undefined,因為babel編譯後開啟了嚴格模式
15 return <h2>我是用函數定義的元件(適用於【簡單元件】的定義)</h2>
16 }
17 //2.渲染元件到頁面
18 ReactDOM.render(<MyComponent/>,document.getElementById('test'))
19 /*
20 執行了ReactDOM.render(<MyComponent/>)
21 1.React解析元件標籤,找到了MyComponent元件。
22 2.發現元件是使用函數定義的,隨後呼叫該函數,將返回的虛擬DOM轉為真實DOM,隨後呈現在頁面中。
23 */
24 </script>
程式碼的中提到了簡單元件。簡單元件(Simple Components):簡單元件通常是功能較為單一、結構簡單的元件,其主要目的是封裝一部分可複用的UI邏輯。它們通常是函陣列件或者使用ES6箭頭函數定義的函陣列件。簡單元件沒有自己的狀態(state),並且通常依賴於父元件傳遞的props來進行渲染。
類元件是使用ES6類語法定義的元件。類元件繼承自React.Component類,並通過render()方法返回一個React元素。類元件可以擁有狀態(state)和生命週期方法。
1 <!-- 準備好一個「容器」 --> 2 <div id="test"></div> 3 4 <!-- 引入react核心庫 --> 5 <script type="text/javascript" src="../js/react.development.js"></script> 6 <!-- 引入react-dom,用於支援react操作DOM --> 7 <script type="text/javascript" src="../js/react-dom.development.js"></script> 8 <!-- 引入babel,用於將jsx轉為js --> 9 <script type="text/javascript" src="../js/babel.min.js"></script> 10 11 <script type="text/babel"> 12 //1.建立類式元件 13 class MyComponent extends React.Component { 14 render(){ 15 //render是放在哪裡的?—— MyComponent的原型物件上,供範例使用。 16 //render中的this是誰?—— MyComponent的範例物件 <=> MyComponent元件範例物件。 17 console.log('render中的this:',this); 18 return <h2>我是用類定義的元件(適用於【複雜元件】的定義)</h2> 19 } 20 } 21 //2.渲染元件到頁面 22 ReactDOM.render(<MyComponent/>,document.getElementById('test')) 23 /* 24 執行了ReactDOM.render(<MyComponent/> 25 1.React解析元件標籤,找到了MyComponent元件。 26 2.發現元件是使用類定義的,隨後new出來該類的範例,並通過該範例呼叫到原型上的render方法。 27 3.將render返回的虛擬DOM轉為真實DOM,隨後呈現在頁面中。 28 * 29 </script>
程式碼的中提到了複雜元件。複雜元件(Complex Components):複雜元件通常包含更多的邏輯和狀態管理,可能由多個簡單元件組成。它們通常是類元件或使用Hooks和其他高階特性定義的元件。複雜元件可以處理更多的資料和互動,並且在其內部可能包含自己的狀態。
在類元件中,可以通過在元件的建構函式中初始化this.state來定義初始狀態。state是一個普通的JavaScript物件,其中包含元件的各個狀態屬性及其初始值(可以部分地理解為類似Vue的響應式資料)。狀態屬性可以根據需要定義多個,並且可以通過this.setState()方法來更新狀態,通過更新元件的state可以更新對應的頁面顯示(重新渲染元件)。
1 <!-- 準備好一個「容器」 -->
2 <div id="test"></div>
3
4 <!-- 引入react核心庫 -->
5 <script type="text/javascript" src="../js/react.development.js"></script>
6 <!-- 引入react-dom,用於支援react操作DOM -->
7 <script type="text/javascript" src="../js/react-dom.development.js"></script>
8 <!-- 引入babel,用於將jsx轉為js -->
9 <script type="text/javascript" src="../js/babel.min.js"></script>
10
11 <script type="text/babel">
12 //1.建立元件
13 class Weather extends React.Component{
14
15 //構造器呼叫1次
16 constructor(props){
17 console.log('constructor');
18 super(props)
19 //初始化狀態
20 this.state = {isHot:false,wind:'微風'}
21 //解決changeWeather中this指向問題
22 this.changeWeather = this.changeWeather.bind(this)
23 }
24
25 //render呼叫1+n次 1是初始化的那次 n是狀態更新的次數
26 render(){
27 console.log('render');
28 //讀取狀態
29 const {isHot,wind} = this.state
30 return <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'},{wind}</h1>
31 }
32
33 //changeWeather呼叫幾次? ———— 點幾次調幾次
34 changeWeather(){
35 //changeWeather放在Weather的原型物件上,供範例使用
36 //由於changeWeather是作為onClick的回撥,所以不是通過範例呼叫的,是直接呼叫
37 //類中的方法預設開啟了區域性的嚴格模式,所以changeWeather中的this為undefined
38
39 console.log('changeWeather');
40 //獲取原來的isHot值
41 const isHot = this.state.isHot
42 //嚴重注意:狀態必須通過setState進行更新,且更新是一種合併,不是替換。
43 this.setState({isHot:!isHot})
44 console.log(this);
45
46 //嚴重注意:狀態(state)不可直接更改,下面這行就是直接更改!!!
47 //this.state.isHot = !isHot //這是錯誤的寫法
48 }
49 }
50 //2.渲染元件到頁面
51 ReactDOM.render(<Weather/>,document.getElementById('test'))
52
53 </script>
以上程式碼是state的使用和修改。其中使用了一個類元件Weather,實現了點選切換天氣文字的效果。其中類元件中必須完成的方法是render方法,render方法會在生成元件範例後,讓元件範例去呼叫,所以this會指向元件範例。而類中的自定義方法,changeWeather()則是作為點選事件的回撥,呼叫者並非是元件範例物件,所以直接使用會出現this指向的問題。這裡有2個解決辦法,1. 如上文顯示,利用bind改變函數指向。2.利用ES6的箭頭函數,讓函數沒有this而去使用上一級的this。
這裡的bind和構造器顯得過於複雜冗餘,在類裡面定義state屬性可以不需要構造器直接定義,函數也用箭頭函數寫出。以下是簡寫形式
1 class Weather extends React.Component{
2 //初始化狀態
3 state = {isHot:false,wind:'微風'}
4
5 render(){
6 const {isHot,wind} = this.state
7 return <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'},{wind}</h1>
8 }
9
10 //自定義方法————要用賦值語句的形式+箭頭函數
11 changeWeather = ()=>{
12 const isHot = this.state.isHot
13 this.setState({isHot:!isHot})
14 }
15 }
這裡注意到,對state的操作都必須用setState來完成,不能直接使用賦值語句去修改。直接修改state會導致react無法監測到資料的變化而不能重新渲染檢視。
在React中,要通知React重新渲染介面,我們需要使用setState來更新元件的狀態。React並沒有像Vue那樣實現資料劫持或使用類似於Object.defineProperty或Proxy的方式來自動監聽資料的變化。
通過呼叫setState函數,我們顯式地告訴React元件的狀態已經發生了變化,然後React會根據最新的狀態值來重新渲染元件。這是因為React使用了虛擬DOM的概念,它會比較前後兩個虛擬DOM樹的差異,並只更新必要的部分,以提高效能。
使用this.state直接修改狀態的方式是不推薦的,因為React無法追蹤到這種變化。相反,我們應該使用setState函數來更新狀態,這樣React才能捕捉到狀態的變化並進行相應的重新渲染。
總結來說,React確實需要通過setState來通知它資料已經發生了變化,從而觸發重新渲染,而不會自動對資料進行監聽或資料劫持。這種方式可以更好地控制和優化渲染過程,同時提供更好的效能。
setState(stateChange, [callback])------ 物件式的setState 第1個引數stateChange為狀態改變物件(該物件可以體現出狀態的更改) 第2個引數callback是可選的回撥函數, 它在狀態更新完畢、介面也更新後(render呼叫後)才被呼叫。上面所用到的都是隻用了第一個引數。setState修改資料的方式是合併物件而不是替換物件,這個也很好理解,如果不是合併而是替換,每次修改一個屬性值就需要在setState中寫入其他所有屬性值,不符合正常的邏輯。setState對於資料的更新在React事件和元件的生命週期中是非同步的,也就是說無法在setState後馬上獲取新的資料
1 export class App extends Component {
2 constructor() {
3 super()
4
5 this.state = {
6 message: "Hello World"
7 }
8 }
9
10 changeText() {
11 // 用法一
12 this.setState({
13 message: "你好啊"
14 })
15 console.log(this.state.message) // Hello World
16 }
17
18 render() {
19 const { message } = this.state
20 return (
21 <div>
22 <h2>{message}</h2>
23 <button onClick={() => {this.changeText()}}>按鈕</button>
24 </div>
25 )
26 }
27 }
以上程式碼顯示出,列印結果仍然是未修改之前的值。而使用setState的回撥就可以存取修改後的state。
1 export class App extends Component {
2 constructor() {
3 super()
4
5 this.state = {
6 message: "Hello World"
7 }
8 }
9
10 changeText() {
11 // 引數二回撥函數可以保證拿到的資料是更新後的
12 this.setState({ message: "你好啊" }, () => {
13 console.log(this.state.message) // 你好啊
14 })
15 }
16
17 render() {
18 console.log("render函數執行")
19 const { message } = this.state
20 return (
21 <div>
22 <h2>{message}</h2>
23 <button onClick={() => {this.changeText()}}>按鈕</button>
24 </div>
25 )
26 }
27 }
原因一
: setState設計為非同步,可以顯著的提升效能
React中的更新機制是基於批次更新的原理。在React中,當觸發多個狀態更新時,React會將這些更新收集起來,並將它們放入一個任務佇列中。然後,在下一個時間段(例如事件迴圈的末尾)React會批次處理任務佇列中的所有更新。用setState會讓render函數重新執行, 如果每次呼叫 setState都進行一次更新,那麼意味著render函數會被頻繁呼叫,介面重新渲染,這樣效率是很低的。
這種批次更新的機制有助於提高效能。通過將多個更新合併為單個更新,可以減少不必要的元件重新渲染次數,以及避免重複計算和佈局操作。同時,這種批次更新機制也可以避免UI閃爍或不一致的問題,因為所有更新都會一起應用於元件。這意味著,即使在一個時間段內多次呼叫setState更新元件狀態,React也會在適當的時機將這些更新合併為單個更新,並在批次處理過程中進行統一的更新操作。這樣,React可以在一個更高效的上下文中處理狀態變化並更新UI。
總結起來,React的更新機制是基於批次更新的原理,它通過將多個更新收集起來,並在適當的時機進行批次處理,以提高效能和避免不必要的重新渲染。這種機制確保了在一個時間段內的多次狀態更新被合併,並在統一的更新過程中應用於元件。
例如下面程式碼, 當點選按鈕時, render函數只會執行一次, 由此可見是有等待多個更新再進行批次處理的
1 export class App extends Component {
2 constructor() {
3 super()
4
5 this.state = {
6 message: "Hello World"
7 }
8 }
9
10 changeText() {
11 this.setState({
12 message: "你"
13 })
14 this.setState({
15 message: "你好"
16 })
17 this.setState({
18 message: "你好啊"
19 })
20 }
21
22 render() {
23 console.log("render函數執行")
24 const { message } = this.state
25 return (
26 <div>
27 <h2>{message}</h2>
28 <button onClick={() => {this.changeText()}}>按鈕</button>
29 </div>
30 )
31 }
32 }
原因二
: 如果同步更新了state,但是還沒有執行render函數,那麼state和props不能保持同步
在React中,元件的render函數負責根據當前的state和props生成對應的UI。當狀態更新時,React會觸發元件的重新渲染,即執行render函數以生成新的UI。
然而,當我們直接修改state的值而沒有呼叫setState函數時,React並不會立即檢測到狀態的變化,也不會自動觸發重新渲染。這意味著,如果在同步更新state後立即存取props,那麼props的值可能仍然是舊的值,與新的state不同步。
為了確保state和props的同步性,我們應該遵循React的更新機制,即使用setState函數來更新狀態。通過setState,React會在適當的時機檢測到狀態的變化,並在下一次渲染時將新的state與props同步。
例如,在一個元件中,有一個名為message的資料被展示在頁面上,並傳遞給子元件進行展示。然後通過呼叫setState同步地修改了message的值。如果在同步的修改完成後,message的值被改變了,但後續的程式碼中出現了報錯的情況,這時候進行偵錯時會發現頁面中的message值被修改,而傳遞給子元件的message並沒有被修改,導致了元件的state和props中的資料不一致。
Props(屬性)是在React中用於傳遞資料和設定資訊的一種機制。元件可以通過props接收從父元件傳遞下來的資料,這樣可以將資料從一個元件傳遞到另一個元件,實現元件之間的通訊。
Props具有以下主要用途:
下面程式碼是類元件裡props的基本使用,通過在標籤上寫屬性來完成props的傳值,在元件內部使用this.props接收
1 class Person extends React.Component{
2 render(){
3 // console.log(this);
4 const {name,age,sex} = this.props
5 return (
6 <ul>
7 <li>姓名:{name}</li>
8 <li>性別:{sex}</li>
9 <li>年齡:{age+1}</li>
10 </ul>
11 )
12 }
13 }
14 //渲染元件到頁面
15 ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
16 ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
17
18 const p = {name:'老劉',age:18,sex:'女'}
19 // console.log('@',...p);
20 // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
21 ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
在使用JS編寫react程式碼時,我們也可以通過一些方式完成props的型別檢查和一些限制,引入prop-types,用於對元件標籤屬性進行限制。其中propTypes 用於限制props的型別,而defaultProps用於給出一些預設值
1 <script type="text/javascript" src="../js/prop-types.js"></script>
2
3 <script type="text/babel">
4 //建立元件
5 class Person extends React.Component{
6 render(){
7 // console.log(this);
8 const {name,age,sex} = this.props
9 //props是唯讀的
10 //this.props.name = 'jack' //此行程式碼會報錯,因為props是唯讀的
11 return (
12 <ul>
13 <li>姓名:{name}</li>
14 <li>性別:{sex}</li>
15 <li>年齡:{age+1}</li>
16 </ul>
17 )
18 }
19 }
20 //對標籤屬性進行型別、必要性的限制
21 Person.propTypes = {
22 name:PropTypes.string.isRequired, //限制name必傳,且為字串
23 sex:PropTypes.string,//限制sex為字串
24 age:PropTypes.number,//限制age為數值
25 speak:PropTypes.func,//限制speak為函數
26 }
27 //指定預設標籤屬性值
28 Person.defaultProps = {
29 sex:'男',//sex預設值為男
30 age:18 //age預設值為18
31 }
32 //渲染元件到頁面
33 ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))
34 ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
35
36 const p = {name:'老劉',age:18,sex:'女'}
37 // console.log('@',...p);
38 // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
39 ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
40
41 function speak(){
42 console.log('我說話了');
43 }
44 </script>
以上的props的書寫過於複雜,對於JS的類,可以使用static寫在class內部。props也可以通過建構函式傳參獲取,以下是props的簡寫形式
1 class Person extends React.Component{
2
3 constructor(props){
4 //構造器是否接收props,是否傳遞給super,取決於:是否希望在構造器中通過this存取props
5 // console.log(props);
6 super(props)
7 console.log('constructor',this.props);
8 }
9
10 //對標籤屬性進行型別、必要性的限制
11 static propTypes = {
12 name:PropTypes.string.isRequired, //限制name必傳,且為字串
13 sex:PropTypes.string,//限制sex為字串
14 age:PropTypes.number,//限制age為數值
15 }
16
17 //指定預設標籤屬性值
18 static defaultProps = {
19 sex:'男',//sex預設值為男
20 age:18 //age預設值為18
21 }
22
23 render(){
24 // console.log(this);
25 const {name,age,sex} = this.props
26 //props是唯讀的
27 //this.props.name = 'jack' //此行程式碼會報錯,因為props是唯讀的
28 return (
29 <ul>
30 <li>姓名:{name}</li>
31 <li>性別:{sex}</li>
32 <li>年齡:{age+1}</li>
33 </ul>
34 )
35 }
36 }
除了類元件以外,函陣列件也可以使用props完成基本的傳值
1 function Person (props){
2 const {name,age,sex} = props
3 return (
4 <ul>
5 <li>姓名:{name}</li>
6 <li>性別:{sex}</li>
7 <li>年齡:{age}</li>
8 </ul>
9 )
10 }
11 Person.propTypes = {
12 name:PropTypes.string.isRequired, //限制name必傳,且為字串
13 sex:PropTypes.string,//限制sex為字串
14 age:PropTypes.number,//限制age為數值
15 }
16
17 //指定預設標籤屬性值
18 Person.defaultProps = {
19 sex:'男',//sex預設值為男
20 age:18 //age預設值為18
21 }
22 //渲染元件到頁面
23 ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
在React中,props是用於從父元件向子元件傳遞資料的一種機制。props被設計為唯讀的,即子元件無法直接修改傳遞給它的props。
這是因為React採用了單向資料流的原則,父元件作為資料的擁有者和管理者,通過props向子元件傳遞資料。子元件只能讀取父元件傳遞的props資料,並基於這些資料進行渲染和互動。
保持props不可修改有以下好處:
如果子元件需要修改傳遞給它的資料,可以通過回撥函數、狀態管理庫(如Redux或Mobx)等方式,讓父元件來處理資料的修改,並通過props將修改後的資料傳遞給子元件。
Refs(參照)是一種用於存取元件範例或DOM元素的機制。Refs提供了一種方式,讓我們能夠直接存取元件或DOM元素,並對其進行操作。元件內的標籤可以定義ref屬性來標識自己。
Refs的主要用途包括:
Refs的使用方法有3種形式:字串形式,回撥函數形式,createRef。
字串形式:直接在標籤裡寫屬性類似於 ref=「名字」的方式,在元件裡使用this.refs獲取
1 class Demo extends React.Component{
2 //展示左側輸入框的資料
3 showData = ()=>{
4 const {input1} = this.refs
5 alert(input1.value)
6 }
7 //展示右側輸入框的資料
8 showData2 = ()=>{
9 const {input2} = this.refs
10 alert(input2.value)
11 }
12 render(){
13 return(
14 <div>
15 <input ref="input1" type="text" placeholder="點選按鈕提示資料"/>
16 <button onClick={this.showData}>點我提示左側的資料</button>
17 <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦點提示資料"/>
18 </div>
19 )
20 }
21 }
在程式碼中字串形式的ref非常的簡單易懂,但是react官方的檔案中卻已經不推薦使用字串形式的ref了,並且提出了一些可能存在的問題,主要是與效能方面相關,具體的討論區連結如下:https://github.com/facebook/react/pull/8333#issuecomment-271648615
回撥形式:在ref中使用回撥函數來定義ref,把ref繫結到this上。
回撥形式的Refs(回撥Refs)在React中具有以下幾個優點:
靈活性:回撥Refs允許我們執行自定義邏輯來處理參照的元件範例或DOM元素。通過回撥函數,我們可以在元件掛載或解除安裝時捕獲參照,並將其儲存在合適的變數中。這使得我們可以根據需要靈活地存取和操作參照。
型別安全:回撥Refs在TypeScript等靜態型別檢查工具中更具型別安全性。通過使用回撥函數引數的型別註解,我們可以準確地指定參照的型別,以便在編譯時捕獲型別錯誤。
更好的可讀性:通過將回撥函數直接傳遞給ref屬性,程式碼更加清晰易懂。我們可以直接在回撥函數中存取參照,並在適當的時候將其儲存在元件範例上,以便在其他方法中使用。
React生命週期相容性:回撥Refs與React的生命週期方法整合得更好。我們可以在元件的掛載、更新和解除安裝階段使用回撥Refs,以便在正確的時機進行參照的捕獲和釋放。
相容未來的API改變:回撥Refs是React推薦的方式,它們在React中被廣泛使用,並且在未來的React版本中仍然會得到支援。使用回撥Refs可以更好地保持程式碼的向後相容性,並使您的程式碼能夠跟隨React的演進而進行適應。
1 class Demo extends React.Component{
2 //展示左側輸入框的資料
3 showData = ()=>{
4 const {input1} = this
5 alert(input1.value)
6 }
7 //展示右側輸入框的資料
8 showData2 = ()=>{
9 const {input2} = this
10 alert(input2.value)
11 }
12 render(){
13 return(
14 <div>
15 <input ref={c => this.input1 = c } type="text" placeholder="點選按鈕提示資料"/>
16 <button onClick={this.showData}>點我提示左側的資料</button>
17 <input onBlur={this.showData2} ref={c=> this.input2 = c } type="text" placeholder="失去焦點提示資料"/>
18 </div>
19 )
20 }
21 }
回撥形式的ref在更新過程中它會被執行兩次,第一次傳入引數 null
,然後第二次會傳入引數 DOM 元素。這是因為在每次渲染時會建立一個新的函數範例,所以 React 清空舊的 ref 並且設定新的。傳入的null是為了確保清空舊的ref,而第二次傳入新的元素則是設定ref。
以下程式碼可以用於測試ref回撥的執行次數,在每次執行回撥時都會列印結果
1 class Demo extends React.Component{
2
3 state = {isHot:false}
4
5 showInfo = ()=>{
6 const {input1} = this
7 alert(input1.value)
8 }
9
10 changeWeather = ()=>{
11 //獲取原來的狀態
12 const {isHot} = this.state
13 //更新狀態
14 this.setState({isHot:!isHot})
15 }
16
17
18
19 render(){
20 const {isHot} = this.state
21 return(
22 <div>
23 <h2>今天天氣很{isHot ? '炎熱':'涼爽'}</h2>
24 <input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>
25 <button onClick={this.showInfo}>點我提示輸入的資料</button>
26 <button onClick={this.changeWeather}>點我切換天氣</button>
27 </div>
28 )
29 }
30 }
圖中的列印結果顯示執行了2次回撥
createRef方法:React.createRef方法可以建立ref並且給元件範例使用,這個方法會建立一個容器,該容器存放的是可以被ref所標識的節點,這個容器是專用的。
1 class Demo extends React.Component{ 2 /* 3 React.createRef呼叫後可以返回一個容器,該容器可以儲存被ref所標識的節點,該容器是「專人專用」的 4 */ 5 myRef = React.createRef() 6 myRef2 = React.createRef() 7 //展示左側輸入框的資料 8 showData = ()=>{ 9 console.log(this.myRef); 10 alert(this.myRef.current.value); 11 } 12 //展示右側輸入框的資料 13 showData2 = ()=>{ 14 alert(this.myRef2.current.value); 15 } 16 render(){ 17 return( 18 <div> 19 <input ref={this.myRef} type="text" placeholder="點選按鈕提示資料"/> 20 <button onClick={this.showData}>點我提示左側的資料</button> 21 <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦點提示資料"/> 22 </div> 23 ) 24 } 25 }