一、三大屬性之一:state
默認狀態下React.Components會給我們定義構造器(類似於無參構造函數一樣),但是默認是把state設置為null的,那么如果我們要自定義初始化的state的話,那么我們就要像(有參構造函數一樣)自定義構造函數了
如何自定義構造函數呢?
先上代碼:
class Weather extends React.Component { constructor(props) { super(props) this.state = {isHot: false} this.changeWeacher = this.changeWeacher.bind(this) } render() { return <h1 onClick={this.changeWeacher}>今天天氣很{this.state.isHot ? '炎熱' : '涼爽'}</h1> } // 不寫 function changeWeacher() { console.log(this) } }
其中的構造函數模塊:
constructor(props) { super(props) this.state = {isHot: false} this.changeWeacher = this.changeWeacher.bind(this) }
一個重要的細節:
自定義構造函數,必須要執行
super(props)
為什么?
並且 為什么super()要放在構造函數最上面執行呢?
ES6語法中,super指代的其實是父類的構造函數,也就是React.Component的構造函數了,
在你調用super( ) 之前,你無法在構造函數中使用this
執行 super(props) 可以使基類 React.Component 初始化 this.props。
另外一個疑問:有時候我們不傳 props,只執行 super(),或者沒有設置 constructor 的情況下,依然可以在組件內使用 this.props,為什么呢?
其實 React 在組件實例化的時候,馬上又給實例設置了一遍 props:
// React 內部 const instance = new YourComponent(props); instance.props = props;
雖然 React 會在組件實例化的時設置一遍 props,但在 super 調用一直到構造函數結束之前,this.props 依然是未定義的(直接報錯了)
class Button extends React.Component { constructor(props) { super(); // ? 我們忘了傳入 props console.log(props); // ✅ {} console.log(this.props); // ? undefined } // ... }
然后因為state要是對象的模式,所以通過以下語句進行初始化
this.state = {isHot: false}
拓展:類中的方法!
render() { return <h1 onClick={this.changeWeacher()}>今天天氣很{this.state.isHot ? '炎熱' : '涼爽'}</h1> }
如果是通過 this.changeWeacher() 的話 報錯
因為在{ } 內是js語句,所以this.changeWeacher()是直接執行了函數,那么onclick得到的就是這個函數執行的結果了,所以這種方式的話,那么這個函數是一個柯里化函數
柯里化函數:該函數接收一個函數作為參數(比如數組的map方法),或者是該函數返回值是一個函數
如果我們定義函數的時候直接通過:
changeWeacher() { console.log(this) }
那么這個時候this是undefined的,因為在babal的模式下,也就是嚴格模式下,changeWeacher函數里面的this是直接執行全局的,但是嚴格模式下不允許這個發生,所以執行的就是undefined
代碼中通過如下方式(通過bind顯示硬綁定this,讓this執行的是該class類的實例了)
this.changeWeacher = this.changeWeacher.bind(this)
在函數中訪問和修改state
當該函數的this不管是通過 箭頭函數 還是通過顯示綁定的方式讓this執行了該實例的話,就可以通過this來訪問state了
changeWeacher() { const isHot = this.state.isHot this.setState({isHot: !isHot}) }
並且只能通過 this.setState的方式改變state,直接賦值操作改變的話,改變不了狀態的(賦值操作可以改變頁面數值,但是內部的狀態是沒有改變的)
state的簡寫方式
class Weather extends React.Component { state = {isHot: false} render() { return <h1 onClick={this.changeWeacher}>今天天氣很{this.state.isHot ? '炎熱' : '涼爽'}</h1> } changeWeacher = () => { const isHot = this.state.isHot this.setState({isHot: !isHot}) } }
-
不適用構造函數初始化state,直接像定義private屬性一樣,直接定義類的屬性
-
通過箭頭函數的方式定義函數(因為即使在babel嚴格模式下,箭頭函數不會收到嚴格模式的約束,在箭頭函數中使用this訪問屬性的話,當前作用域找不到的話,就在外面作用域找
-
所以就可以在箭頭函數的this中直接訪問實例中定義的state了
-
二、三大屬性之二:props
props的基本使用
<script type="text/babel"> class Person extends React.Component{ render() { return ( <ul> <li>年級1:{this.props.name}</li> <li>年級2:{this.props.sex}</li> <li>年級3:{this.props.age}</li> </ul> ) } } // ReactDOM.render(<Person age="19" />, document.getElementById('test')) // ReactDOM.render(<Person age={19}/>, document.getElementById('test')) const p = {name : 'gogocj', age: '19', sex: '男'} ReactDOM.render(<Person {...p} />, document.getElementById('test')) </script>
-
在類中中直接通過this.props訪問傳遞過來的參數
-
如果是在jsx中的話,就要在 { } 中來訪問this.props
-
-
傳遞參數相關
-
如果想要傳輸數字的話,要通過 <Person age={19}/>,也就是通過一個{ } 因為在{ }中式js語句,所以19就是js語句中的數字了
-
通過{ ...p } 三點運算展開符的方式來傳遞props
-
限制props的類型和默認值
class Person extends React.Component{ ........ } Person.propTypes = { name: PropTypes.string.isRequired, speak: PropTypes.func } Person.defaultProps = { sex: '未知' }
使用繼承了React.Component的類自帶的 propTypes屬性
-
要求是串聯的:
PropTypes.string.isRequired // 表示類型是字符串,並且是必須傳的
-
默認值通過 自帶的defaultProps屬性
簡寫方式
上面都是直接通過Person.propTypes的方式來對規定類型限制的:
但是Person就是當前的類,所以完全可以省略Person,直接在這個類的內部定義一個static對象就可以了
class Person extends React.Component{ ............ static propTypes = { name: PropTypes.string.isRequired, speak: PropTypes.func } static defaultProps = { sex: '未知' } }
在構造器中使用props
constructor(props) { super(props) // 使用props }
-
如果要在構造器中使用的話就必須寫 super(props) ,不然在構造器中使用props的話,就會直接的報錯了
-
也就是說,構造器中不適用props就可以不定義,使用的話就要super一下
三、三大屬性之三:refs
前言:
React的誕生很多都是為了減少對document的使用,而我們如果在js中要獲取到對應元素的話,傳統的方法都是直接使用document的getlementbyid,byClass等等,到那時在React為了減少document操作,使用的是refs
字符串類型的refs
-
class Demo extends React.Component{ showData = () => { const {input01} = this.refs alert(input01.value) } render() { return ( <div> <input id="input1" ref="input01" type="text" placeholder="點擊按鈕提示數據"/> <button onClick={this.showData}>點我提示左側的數據</button> </div> ) } }
我們通過在jsx中,用ref來表示jsx中程序員寫的虛擬DOM,但是我們通過this.refs獲取到的元素並不是虛擬DOM,而是虛擬DOM轉化成真實DOM之后的節點
-
通過this.refs.input01就可以拿到這個input元素了
拓展:React對原生html的相關瘋轉
在原生里面使用 onclick、onblur等等,但是在React中使用的是onClick、onBlur等等
為什么呢?
React其實是在原生的onclick等等的基礎上,進行了相關兼容性的封裝,然后改了一下名字,也就是第二個單詞首字母大寫了,也就是onClick
回到函數形式的refs
前言:
字符串形式的ref可能在未來就廢除了,因為過多的給原生定義refs來獲取該元素信息,方便但是性能太低了
class Demo extends React.Component{ showData = () => { const {input01} = this alert(input01.value) } render() { return ( <div> <input ref={ c => this.input01 = c} type="text" placeholder="點擊按鈕提示數據"/> <button onClick={this.showData}>點我提示左側的數據</button> </div> ) } }
-
直接在ref中調用箭頭函數,參數就是該元素(代碼中用c表示),直接通過this.input01 = c吧這個元素掛載到this上了,因為是箭頭函數,所以此時的this執行的是該類實例。
使用內聯函數的ref
class Demo extends React.Component{ saveInput = (c) => { this.input1 = c console.log('@',c) } render() { return ( <div> <input ref={ this.saveInput } type="text"/> <button onClick={this.showInfo}>點我</button> </div> ) } }
但是這種內聯函數是有一個問題的
-
saveInput中:就是在狀態更新的時候會執行兩次,第一次c拿到的是null,第二次拿到的才是該元素,更新指的是狀態的更新,也就是再一次調用render函數,但是點擊和刷新就不會出現這個問題了 ,這是因為在每次渲染render的時候都會創建一個新的函數實例,所以React清空舊的並且設置新的。
另一種方式:React.createRef
class Demo extends React.Component{ myRef1 = React.createRef() myRef2 = React.createRef() showData1 = () => { alert(this.myRef1.current.value) } showData2 = () => { alert(this.myRef2.current.value) } render() { return ( <div> <input ref={this.myRef1} type="text" placeholder="點擊按鈕提示數據1"/> <button onClick={this.showData1}>點我提示左側的數據</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦點提示數據"/> </div> ) } }
-
ref={this.myRef} 調用之后,自動把該標簽放到這個容器里面
-
但是該容器是“專人專用”的,如果多個標簽同時使用的話,那么后放進去的標簽就會把前面的頂掉了
-
雖然這個是要用多少個就要掛載多少個在實例上,但是這種方式是目前react最推薦的一種方式了