接著是組件實例三大核心屬性之二 props。
基本使用
首先來說最簡單的使用方法,我們可以先在網頁中加入三個 div 區塊:
<div id="test"></div> <div id="test1"></div> <div id="test2"></div>
然後在 ReactDOM.render()
中,往組件標籤裡面初始化(name="Tom" age="18" sex="女"
),Person 類中的 render 就可以在對應位置加上 {this.props.屬性名稱}
去獲取值,或是用 const {name, age, sex} = this.props
的方式。
class Person extends React.Component { render() { const {name, age, sex} = this.props // 多級結構用小括號比較整潔 return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } } // 渲染組件到頁面 ReactDOM.render( // 把 name:"Tom" 放在了 props <Person name="Tom" age="18" sex="女"/>, document.getElementById('test') ) ReactDOM.render( <Person name="Jerry" age="25" sex="男"/>, document.getElementById('test1') ) ReactDOM.render( <Person name="Sandy" age="33" sex="女"/>, document.getElementById('test2') )
但是這樣有幾個問題:
- 如果人的訊息特別多怎麼辦?
- 在真實開發中,人的訊息通常是伺服器返回的,而我們需要請求把數據取回呈現在頁面上。我們用一個 p 變數來模擬這個場景,取得伺服器回傳的內容後,我們這麼寫也是可以:
const p = {name:'Sandy',age:33,sex:'女'} ReactDOM.render( <Person name={p.name} age={p.age} sex={p.sex} />, document.getElementById('test2') )
但一樣是回到第一個問題,有沒有更好的方式解決呢?
我們可以這麼去寫:
const p = {name:'Sandy',age:33,sex:'女'} ReactDOM.render( <Person {...p}/>, document.getElementById('test2') )
用 <Person {...p}/>
的方式省去一個一個慢慢寫的時間以及減少程式碼複雜度。
前提是我們在 render 中設的屬性名和我們從伺服器接收的值屬性名稱必須相同:
下面補充一下展開運算符的用處。
展開運算符
與 JAVA 的可變參數是同樣概念。
展開運算符的用處:
- 連接兩個陣列
let arr1 = [1,2,3,4,5,6,7,8] let arr2 = [2,4,5,6,7,9,0] // 將 arr1 的元素展開放前面 , 將 arr2 的元素展開接在後面 let arr3 = [...arr1,...arr2] console.log(arr3) // [1, 2, 3, 4, 5, 6, 7, 8, 2, 4, 5, 6, 7, 9, 0]
- 分別輸出陣列元素
- 比如我有這麼兩個陣列,我使用展開運算符輸出陣列以及直接輸出陣列試試看會輸出什麼結果:
<script type="text/javascript"> let arr1 = [1,2,3,4,5,6,7,8] let arr2 = [2,4,5,6,7,9,0] console.log(...arr1) console.log(arr1) console.log(...arr2) console.log(arr2) </script>
- 函數傳參
function sum(...numbers) { console.log('@',numbers) } console.log(sum(1,2,3,4)) // @[1, 2, 3, 4]
配合 reduce 寫一個將所有值相加的方法
function sum(...numbers) { return numbers.reduce((preValue,currentValue)=>{ return preValue + currentValue }) } console.log(sum(1,2,3,4)) // 10 console.log(sum(1,2,3,4,5,6,7,8,9,10)) // 55
- 不能用作展開物件
- React + babel 可以用
...物件
展開一個物件,但是在原生 JS 中不能使用...物件
展開物件。 - 並且原生的
{...p}
和在React<Person {...p}/>
中的{...p}
大括號用意不同,React 中的大括號用作分隔符使用,原生的大括號則是提前定義好的語法結構。
- React + babel 可以用
// 將物件賦值給另一個變數 是引用關係傳遞 let person = {name:'Tom',age:18} let person2 = person // 引用關係傳遞 // console.log(...person) // 錯誤 person.name = "Jerry" console.log(person2.name) // Jerry // 將物件用大括號括起來 則是複製一個物件 let person = {name:'Tom',age:18} let person2 = {...person} // 複製一個物件 person.name = "Jerry" console.log(person2)
props的限制
當今天我們對傳遞的參數有以下要求時:
- 對傳遞的標籤屬性進行型別限制
- 進行必要性限制
- 對某屬性指定默認值
那麼我們可以使用 propTypes 來解決。
React.PropTypes自 React v15.5 以來已轉移到不同的包中,不能再使用 React.PropTypes 去使用,需要使用這個 prop-types 導入到網頁中。
我們需要 import prop-types
<script type="text/javascript" src="https://unpkg.com/prop-types@15.6.2/prop-types.js"></script>
然後這麼去使用它
class Person extends React.Component { render() { // 多級結構用小括號比較整潔 const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } } // 限制型別和必要性 Person.propTypes = { name : PropTypes.string.isRequired, // name 必填, 且限制為字串型別 sex : PropTypes.string, // sex 限制為字串 age : PropTypes.number, // age 必須為數值 }
name : PropTypes.string.isRequired
代表限制 name 屬性傳進來的值必須是字串型別並且必填,若我今天在 name 屬性傳入一個數值則會出錯:
const p = {name:100,age:33,sex:'女'} ReactDOM.render( <Person {...p}/>, document.getElementById('test2') )
意思是,name屬性應該傳入字串,但是卻傳了數值。
那麼如何設定 props 的初始值呢?
可以使用 defaultProps
:
Person.defaultProps = { sex : '男', age : 18 }
如果不傳入年齡和性別,則預設是 18 男。
props的簡寫
現在我們寫好的類和props限制長這樣:
class Person extends React.Component { render() { // 多級結構用小括號比較整潔 const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } } // 限制型別 Person.propTypes = { name : PropTypes.string.isRequired, // name 必填, 且限制為字串型別 sex : PropTypes.string, age : PropTypes.number, } // 給定默認值 Person.defaultProps = { sex : '男', age : 18 }
我們要如何把型別限制和給定默認值這兩段和類融在一起呢?
把 propTypes 和 defaultProps 丟進 class 中,前面加上 static
:
class Person extends React.Component { // 限制型別 static propTypes = { name : PropTypes.string.isRequired, // name 必填, 且限制為字串型別 sex : PropTypes.string, age : PropTypes.number, } // 給定默認值 static defaultProps = { sex : '男', age : 18 } render() { // 多級結構用小括號比較整潔 const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } }
首先我們要知道一個概念,當我們在類別中加上A屬性、B方法然後去 new 物件時,每個物件都會有自己的A屬性和B方法,但是某些情況上來講,有些屬性和方法它是固定的,並不需要每個物件都各自擁有,因此可以宣告為 static,表示它屬於類別。
所以我們在 propTypes 和 defaultProps 前面加上 static,代表這兩個方法屬於類別,不需要每個物件都各自擁有。
建構子與props
這部分就是個補充,可以直接看小結。
一樣用上述例子,我們在類中加上建構子:
class Person extends React.Component { constructor(props) { console.log(props) super(props) } static propTypes = { name : PropTypes.string.isRequired, sex : PropTypes.string, age : PropTypes.number, } static defaultProps = { age : 18, sex : '男', } render() { const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } } ReactDOM.render( <Person name="Jerry" age={19} sex="男"/>, document.getElementById('test') ) ReactDOM.render( <Person name="Tom" age={18} sex="男"/>, document.getElementById('test1') ) const p = {name:"Sandy"} ReactDOM.render( <Person {...p}/>, document.getElementById('test2') )
通常在React中,建構子僅用於以下兩種情況:
- 通過給 this.state 賦值物件來初始化內部state
比如在state那節中寫到的:this.state = {isHot:true,wind:'微風'}
- 為事件處理函數綁定實例
比如在state那節中寫到的:this.changeWeather = this.changeWeather.bind(this)
但是,在 state 的簡化部分我們也提到
可以把 this.state = {isHot:true,wind:'微風'}
從建構子搬出來到類中
也可以把 this.changeWeather = this.changeWeather.bind(this)
刪掉,將 changeWeather 改為箭頭函數就能解,那麼我們好像真的沒有必要使用到建構子了。
用建構子接收與不接受 props、super() 和 super(props) 有什麼區別?
官方文檔中提到:
一個 React component 的 constructor 會在其被 mount 之前被呼叫。當你為一個 React.Component subclass 建立 constructor 時,你應該在其他任何宣告之前呼叫 super(props)。否則,this.props 在 constructor 中的值會出現 undefined 的 bug。 — 引用自官方文檔
當我們的建構子接收了 props 並且在其中寫了 super(props),然後輸出 this.props,能正常接收到值;但若是建構子不接收 props 並且 super(),輸出 this.props 則會接收到 undefined。
小結:
- 在開發的時候能省略建構子就省略,幾乎不寫。
- 建構子是否接受props,是否傳遞給super,取決於是否希望在建構子中通過this訪問props。
函數式組件使用props
在這之前好像一直提到組件實例三大屬性 – state、props、refs,因為實例兩個字所以圍繞在類式組件上,但是 props 這個屬性其實是可以用在函數式組件的。
這是因為,函數能夠接收參數。
一樣用之前的例子,我們要如何把這個類改成函數呢?
class Person extends React.Component { constructor() { super() console.log(this.props) } // 限制型別 static propTypes = { name : PropTypes.string.isRequired, // name 必填, 且限制為字串型別 sex : PropTypes.string, age : PropTypes.number, } // 給定默認值 static defaultProps = { age : 18, sex : '男', } render() { // 多級結構用小括號比較整潔 const {name, age, sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } }
寫成這樣?
function Person() { return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) }
但name, age, sex屬性,類是從組件實例物件的props中取出;函數式組件並沒有this,所以沒辦法這麼去寫。
**不過,函數可以接收參數。**
因此,函數的寫法也一樣是接收 props:
function Person(props) { const {name,age,sex} = props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) }
那限制型別和初始化也是和類式組件一樣的寫法,只是沒辦法像類一樣融合在一起,必須寫在外面:
function Person(props) { const {name,age,sex} = props return ( <ul> <li>姓名:{name}</li> <li>性別:{sex}</li> <li>年齡:{age}</li> </ul> ) } Person.propTypes = { name : PropTypes.string.isRequired, sex : PropTypes.string, age : PropTypes.number, } Person.defaultProps = { age : 18, sex : '男', }
總結
- 每個組件物件都會有 props 屬性。
- 組件標籤的所有屬性都保存在 props 中。
- 通過標籤屬性從組件外向組件內傳遞變化的數據。
- 注意:組件內部不要修改 props 數據。
- 可以使用 prop-types 庫進行參數限制(需要引入 prop-types 庫)。
- 展開運算符:將物件的所有屬性通過 props 傳遞。