組件
1.函數式組件
-
什么是函數式組件
創建一個函數,只要函數中返回一個新的JSX元素,則為函數式組件
import React from 'react'
function News(props){ // 聲明函數式組件
console.log(props); //接收只讀屬性
return <div>
<ul>
<li>webpack</li>
<li>vue</li>
<li>react</li>
</ul>
</div>
}
export default News;
-
調用組件
可以是單閉合,也可以是雙閉合。雙閉合方式可以把一些子節點當作屬性(children)傳遞給組件,在組件中可以把傳遞的這些節點放在指定的位置
// index.js
ReactDOM.render(<>
<Dialog con='嘿嘿嘿' />
<Dialog con='呵呵呵' lx={1} >
<span>1</span>
<span>2</span>
</Dialog>
</>, document.getElementById('root'));
// Dialog.js
export default function Dialog(props) {
let {con, lx = 0, children, style = {}} = props,
title = lx === 0 ? '系統提示' : '系統警告';
return <section style={style}>
<h2>{title}</h2>
<div>{con}</div>
{/*把屬性中傳遞的子元素放到組件中的指定位置*/}
{ children }
{/*也可以基于REACT中提供的專門遍歷CHILDREN的方法來完成遍歷操作*/}
{
React.Children.map(children, item => item)
}
</section>;
};
-
靜態組件
每次調用函數組件,都會重新進行渲染和計算,把渲染后的結果呈現在頁面中,渲染完成后呈現的內容將不再改變,除非重新調用該組件
2.類組件
-
什么是類組件
創建一個類,讓其繼承React.Component
或者React.PureComponent
,此類被稱為類組件 -
基于狀態管理動態組件:
1.設置初始狀態值
2.修改狀態:setState
修改組件中的狀態
import React from 'react'
export default class Clock extends React.Component{
// 調取組件,創建類的一個實例,首先執行constructor,把屬性、上下文等信息傳遞進來
constructor(props){
super(props);
// 如果只寫SUPER():雖然創建實例的時候把屬性傳遞進來了,但是并沒有傳遞父組件,也就是沒有把屬性掛載到實例上,使用THIS.PROPS獲取的結果是UNDEFINED
// 如果SUPER(PROPS):在繼承父類私有的時候,就把傳遞的屬性掛載到了子類的實例上,CONSTRUCTOR中就可以使用THIS.PROPS了
console.log(this.props); // 接收的只讀屬性
// 創建初始狀態
this.state = {
time: new Date().toLocaleString()
}
}
// render渲染組件的內容
render(){
return <div>
{this.state.time}
</div>
}
// componentDidMount:生命周期 第一次渲染完
componentDidMount(){
setInterval(() => {
// 修改狀態,并且通知組件重新渲染
this.setState({
time: new Date().toLocaleString()
});
}, 1000);
}
-
屬性的操作
利用第三方插件prop-types
可以設置屬性的規則
// test.jsx
import PropTypes from 'prop-types';
...
// 設置默認屬性
static defaultProps = {
m: 100
};
// 屬性驗證
static propTypes = {
m: PropTypes.number,
x: PropTypes.string.isRequired
};
...
// index.js
...
<Clock m={1} x='pass'/>
...
-
非受控組件
不受狀態管控的組件(通過ref方式),有時我們會需要直接對某個DOM節點或組件進行操作,而不是通過狀態,此時會運用到非受控組件,對應的概念:受控組件:受狀態管控的組件 => 數據驅動視圖渲染
// 以下是三種使用ref的方法
export default class Input extends React.Component {
constructor(){
super();
this.refObj = React.createRef(); // { current: null } method 3
}
render() {
return <div>
<input type="text" ref='inpBox' /> // method 1,不推薦使用
<input type="text" ref={element => { // method 2
// element當前的元素對象
this.inp = element;
}} />
<input type="text" ref={ this.refObj } /> // method3
</div>;
}
componentDidMount() {
this.refs.inpBox.focus(); // method 1,不推薦使用
this.inp.focus(); // method 2
this.refObj.current.focus(); // method3
}
}
3.細節知識點
- REACT中的事件是合成事件,即所有的事件都是進行事件代理的,而且事件對象也是合成的,故會出現以下情況。解決這一問題的方法有兩種,一是采用bind改變this指向,二是采用ES6的箭頭函數
render(){
return <div>
<button onClick={this.handle}>button</button>
</div>
}
handle(ev){
console.log(this); // undefined
console.log(ev); // 事件對象
}
<button onClick={this.handle.bind(this)}>button</button> //method 1
<button onClick={()=>console.log(this)}>button</button> //method 2
- 當使用
setState
進行狀態設置時,即使狀態不發生變化,仍然會觸發render的重新渲染,此時應當考慮對其進行優化,可以在shouldComponentUpdate
中進行手動對比設置,也可以直接讓類組件繼承React.PureComponent
來自動進行淺對比(引用變化是檢測不出來的)
handle = ev => {
this.setState({
// 不管狀態是否改變,都會控制render重新渲染
});
}
// 手動對比優化,與自動對比同時出現時手動為主
shouldComponentUpdate(nextProps, nextState) {
// 拿當前的狀態和最新修改的狀態進行對比(淺對比),如果一樣則不渲染,不一樣才進行渲染
if (this.state.n === nextState.n) {
return false;
}
return true;
}
// 自動對比
export default class Test extends React.PureComponent {
...
}
...
-
setState
本身在生命周期函數或者合成事件中執行是異步的
=>保證REACT生命周期函數執行的順序不會紊亂
=>保證其實現渲染隊列的機制,可以合并setState
后統一處理
export default class Test1 extends React.Component {
state = {
n: 0
};
render() {
console.log("render")
return <div>
{this.state.n}
<button onClick={this.handler}>+</button>
</div>
}
handler = ev => {
this.setState({
n: this.state.n + 1
})
console.log('ok')
}
}
點擊按鈕后
-
setState
在原生事件綁定中和其他異步操作中是同步的
=>此時失去渲染隊列的效果
export default class Test1 extends React.Component {
state = {
n: 0,
m: 0
};
render() {
console.log('render')
return <div>
{this.state.n} === {this.state.m}
<button onClick={this.handler}>+</button>
</div>
}
handler = ev => {
setTimeout(()=>{
this.setState({
n: 10
})
this.setState({
m:20
})
console.log('ok')
},1000)
}
}
失去渲染隊列處理效果
- 按理說ES6中的類是不能設置靜態屬性的,但是WEBPACK打包編譯的時候會根據
babel-preset-react
將其轉換為復合規范的語法
// 以下寫法理應報錯,但是webpack編譯后可正常運行
...
static defaultProps = {
m: 100
};
static propTypes = {
m: PropTypes.number,
x: PropTypes.string.isRequired
};
...
- 通過
onChange
事件實現MVVM雙向綁定
...
<input type="text" className='form-control'
value={text}
onChange={ev => {
this.setState({
text: ev.target.value
});
}}/>
...
4.生命周期
1.第一次調用組件渲染的周期流程
1.1.給屬性設置默認值(設置默認規則)
1.2.constructor
=> 設置初始的狀態等
1.3.componentWillMount
第一次掛載之前 => 向服務器發送數據請求
1.4.render
渲染
1.5.componentDidMount
第一次掛載之后 => 把虛擬DOM轉換為真實DOM了,我們可以獲取DOM元素進行操作
2.當組件狀態發生改變 setState
2.1.shouldComponentUpdate(nextProps, nextState)
是否允許當前組件重新渲染(返回TRUE則繼續重新渲染,返回FALSE則停止重新渲染)
2.2.componentWillUpdate(nextProps, nextState)
重新渲染之前
2.3.render
重新渲染
2.4.componentWillUpdate
重新渲染之后
3.當組件屬性發生改變:父組件重新傳遞最新的屬性信息
3.1.componentWillReceiveProps(nextProps, nextState)
在接受最新的屬性之前
3.2....(重復2)
4.componentWillUnmount
卸載組件之前
export default class Test extends React.Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {
data: [],
n: 0
};
}
componentWillMount() {
console.log('componentWillMount');
setTimeout(() => {
this.setState({
data: [100, 200]
});
}, 5000);
}
render() {
console.log('render');
let { data, n } = this.state;
return <div>
{data} === {n}
<button onClick={() => {
this.forceUpdate(); <!-- 調用該方法時直接跳過shouldComponentUpdate階段 -->
}}>強制更新</button>
</div>;
}
componentDidMount() {
console.log('componentDidMount');
}
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate', nextProps, nextState);
// nextProps nextState 最新要修改的屬性和狀態
// this.state / this.props 修改之前的
// this.forceUpdate() 不會執行這個周期函數,會強制更新當前組件
return true;
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
}