react 中通過 props 和 state 實現組件間的通信,對數據進行傳遞、操作。
1. 屬性 props
在組件中可以通過 props 傳遞數據。
正常情況下,props是外部傳入的,組件內部也可以通過一些方式來設置屬性的默認值,屬性不能被組件自己更改,但是你可以通過父組件主動重新渲染的方式來傳入新的 props。
通俗來講,就是在使用一個組件的時候,可以把參數放在標籤的屬性當中,所有的屬性都會作爲組件 props
對象的鍵值。通過箭頭函數創建的組件,需要通過函數的參數來接收props
。
來看一個使用 props 傳參的例子:
import React from 'react';
import ReactDOM from 'react-dom';
//函數組件
function Box(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <Box name="React"/>;
ReactDOM.render(
element,
document.getElementById('root')
);
1.1 設置默認值 defaultProps
在類組件中,可以通過組件類的 defaultProps 屬性爲 props 設置默認值。在父組件沒有指定其值時,會使用這個默認值。propTypes 類型檢查發生在 defaultProps 賦值後,所以類型檢查也適用於 defaultProps。
import React from 'react';
import ReactDOM from 'react-dom';
class Box extends React.Component {
//方式一:在類的內部設置靜態屬性
static defaultProps={
name:'react'
}
render() {
return (
<h1>Hello, {this.props.name},age:{this.props.age}</h1>
);
}
}
//方式二:在類的外面 設置defaultProps屬性
Box.defaultProps = {
age: 18
};
ReactDOM.render(
<Box/>,
document.getElementById('root')
);
1.2 props.children
每個組件都可以獲取到 props.children。它包含組件的開始標籤和結束標籤之間的內容。類似vue中的插槽。
看下面這個例子:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Content extends Component {
render() {
return (
<div>{this.props.children}</div>
);
}
}
class Title extends Component {
render() {
return (
<div>歡迎進入{this.props.children} </div>
);
}
}
ReactDOM.render(
<Content>
<Title>React</Title>
<p>這裏是內容</p>
</Content>,
document.getElementById('root')
);
上面的代碼中,在頁面中渲染Content 的 props.children,最終會渲染出<Content></Content>標籤中的所有內容,渲染頁面的結果如下:
1.3 proptypes 類型檢查
Props 驗證使用 propTypes,它可以保證我們的應用組件被正確使用,React.PropTypes 提供很多驗證器 (validator) 來驗證傳入數據是否有效。當向 props 傳入無效數據時,JavaScript 控制檯會拋出警告。
React.PropTypes 在 React v15.5 版本後已經移到了prop-types 庫。
在終端中執行下面的命令,安裝 prop-types 插件
yarn add prop-types -S
使用:
- 使用 import 導入 prop-types。
- prop-types 只能在類組件中做驗證,不能在函數組件中做驗證。
- 通過
類名.propTypes={}
,來定義屬性規則,固定寫法,注意大小寫。 - 類名.propTypes={} 中,鍵名爲屬性名,值爲對屬性的驗證規則,例如Proptypes.string等。
- 可以指定一個自定義驗證器。它在驗證失敗時應返回一個 Error 對象。自定義驗證時,對象值爲一個函數,函數的參數可以通過 arguments 打印。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Proptypes from 'prop-types';
class Box extends Component {
render() {
return (
<div>
姓名: {this.props.name},年齡:{this.props.age}
</div>
);
}
}
Box.propTypes={
name:Proptypes.string.isRequired, //表示name爲字符串類型,isRequired 必須要傳
age:function(props,propName){ //自定義驗證
if(props[propName]<20){
return new Error(
'Invalid prop `' + propName + '` Because it Less than 20.'
)
}
}
}
ReactDOM.render(
<Box name="xiaoming" age={18} />,
document.getElementById('root')
);
prop-types 的驗證規則:
規則 | 說明 |
---|---|
.array | 輸入的類型爲數組 |
.bool | 輸入的類型爲布爾 |
.func | 輸入的類型爲函數 |
.number | 輸入的類型爲數值 |
.object | 輸入的類型爲對象 |
.string | 輸入的類型爲字符串 |
.symbol | 輸入的類型爲symbol類型 |
以上爲 js 的原生類型
規則 | 說明 |
---|---|
.node | 表示任何可被渲染的元素(包括數字、字符串、元素或數組) (或 Fragment) 也包含這些類型。 |
.element | 表示一個 React 元素,確保傳遞給組件的 children 中只包含一個元素。 |
.elementType | 表示一個 React 元素類型,即上面案例中的 Box |
.instanceOf() | 聲明 prop 爲是否爲類的實例,這裏使用 JS 的 instanceof 操作符 |
.oneOf() | 指定 prop 只能是特定的值,指定它爲枚舉類型 |
.oneOfType() | 一個對象可以是幾種類型中的任意一個類型 |
.arrayOf() | 指定一個數組由某一類型的元素組成,例如只能由數字組成的數組 .arrayOf(PropTypes.number) |
.objectOf() | 指定一個對象由某一類型的值組成,使用方法同 .arrayOf() |
.shape() | 指定一個對象由特定的類型值組成 |
.isRequired | 在任何 PropTypes 屬性後面加上 isRequired ,確保這個 prop 沒有被提供時,會打印警告信息 |
部分語法使用如下:
import PropTypes from 'prop-types';
Box.propTypes = {
// 你可以讓你的 prop 只能是特定的值,指定它爲
// 枚舉類型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 一個對象可以是幾種類型中的任意一個類型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 可以指定一個數組由某一類型的元素組成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 可以指定一個對象由某一類型的值組成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 可以指定一個對象由特定的類型值組成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
2. 狀態 state
state 與 props 類似,但是 state 是私有的,並且完全受控於當前組件。
可以在類的構造函數中初始化狀態 state,也可以在類中直接定義屬性(下面2.1中的寫法)來初始化狀態 state 。通過 this.state 獲取 state 中的數據內容。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Box extends Component {
constructor() {
super();
this.state = {
name: 'xiaoming',
age: 18
}
}
render() {
return (
<div>
姓名: {this.state.name},年齡:{this.state.age}
</div>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('root')
);
2.1 setState()
修改 state 中的數據內容,有以下三種方式:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Box extends Component {
state={
name:"xiaoming"
}
componentDidMount(){
//第一種修改方式
this.setState({name:'zhangsan'});
//第二種修改方式
this.state.name="sssss";
this.setState({});
//第三種修改方式
this.setState((preState,props)=>{
return{
name:preState.name+'ccc'
}
},()=>{
//數據修改完成後的回調函數
})
}
render() {
return (
<div>姓名: {this.state.name}</div>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('root')
);
當使用第二種方式的時候,雖然修改後的數據也在瀏覽器端渲染出來了,但是在控制檯中會輸出下面的警告信息,所以不建議直接使用 this.state 修改數據。
react 和 vue 中對 state 中數據的修改都是異步的,在vue中可以使用$nextTick() 方法,獲取修改後的數據內容,在 react 中通過使用上面的第三種方法,獲取修改後的數據內容。
setState 對數據的更新,會做 merge 合併的操作,即不會覆蓋原來的數據內容,會把你提供的對象合併到當前的 state中。
2.2 狀態提升
如果多個組件要實現數據共享,可以將數據提升到父組件中,對數據的操作統一在父組件中進行。
3. 屬性和狀態的區別
props 和 state 的相似點:
- 都是 js 對象,更新數據後會都會觸發 render() 更新;
- 都可以設置默認值;props 通過 defaultProps 屬性設置默認值;state 直接在定義的時候設置初始值;
props 和 state 的不同點:
- 屬性是從父組件獲取的,狀態是在當前組件中定義的;
- 屬性值只能由父組件修改,狀態值只能由當前組件修改;
- 屬性主要用於父組件和子組件間的通信,在組件內部是無法修改參數的;
- 狀態是給自己用的,在內部初始化,被自己修改,在外部是不能修改的,內部通過 setState 修改,會觸發render函數;
總結:state 和 props 主要的區別在於 props 是不可變的,而 state 可以根據與用戶交互來改變。這就是爲什麼有些容器組件需要定義 state 來更新和修改數據。 而子組件只能通過 props 來傳遞數據。