setState()
函數用於更新組件的狀態,有對象式和函數式兩種寫法。javascript
// 對象式
this.setState({
count: this.state.count + 1
})
// 函數式
this.setState((state, props) => {
count: state.count + 1
})
複製代碼
函數式寫法能夠直接從參數接收到當前組件的 state
和 props
,更方便對狀態進行處理。不管哪一種寫法,setState()
的第二個參數均可以接收一個回調函數。css
this.setState({
count: this.state.count + 1
}, () => { // 回調函數
console.log(this.state.count)
})
複製代碼
setState()
對狀態的更新是異步的,回調函數會在狀態變動完成後觸發。也就是說若是直接在 setState()
調用以後去輸出一個狀態值,那麼頗有可能拿到的仍是變動以前的值。java
Hooks 是 16.8
版本開始推出的針對函數式組件的功能。函數式組件沒有 this
,因此沒法操做 state
、refs
和生命週期鉤子,而 Hooks 解決了這些問題。實際使用來看,並無類組件寫着舒服,因此仍是老實用類組件吧!react
export default function Demo() {
const [name, setName] = React.useState('zidu')
changeName() {
// 操做 state
setName('CallbackZidu')
}
return (
<div> <h2>用戶名:{name}</h2> <button onClick={changeName}>更名</button> </div>
)
}
複製代碼
使用 React.useState()
能夠建立出一個 state
,示例中使用的是數組解構賦值語法,name
就是這個狀態的名字,setName
就是操做這個狀態的函數。若是你有多個狀態須要維護,就須要屢次使用 React.useState()
來建立這些狀態,固然你也能夠直接建立一個對象類型的狀態來統一管理,可是這裏有個大坑。編程
經過 React.useState()
拿到的 setXxx
函數對於狀態改變的處理邏輯是不一樣於 setState()
函數的。在 setState()
函數中容許只改變多個狀態中某些狀態的值。也就是說 setState()
函數拿到返回的對象後會和以前的 state
進行合併。可是 setXxx
不會合並,而是作替換操做。數組
const [all, setAll] = React.useState({
name: 'zidu',
age: 27
})
setAll({
// 儘管不變動 name 的值,可是這裏仍是要用原來的值進行賦值,不然函數執行完後 name 就不存在了。
name: all.name,
age: 28
})
複製代碼
Effect Hook 就是使用函數來模擬組件的生命週期,可是也只能模擬出三個生命週期,並且爲了區別出不一樣的生命週期,寫法有點繞。服務器
React.useEffect(() => {
console.log('只傳一個函數,模擬 componentDidUpdate 生命週期鉤子')
})
複製代碼
若是 React.useEffect()
只接收到了一個箭頭函數做爲參數,那麼箭頭函數就是 componentDidUpdate
生命週期鉤子。markdown
React.useEffect(() => {
console.log('第二個參數是空數組,模擬 componentDidUpdate 生命週期鉤子')
}, [])
複製代碼
若是接收到的第二個參數是空數組,那麼箭頭函數就是 componentDidMount
生命週期鉤子。react-router
React.useEffect(() => {
console.log('數組有元素,模擬 componentDidUpdate 和 componentDidUpdate 兩個生命週期鉤子')
}, [name])
複製代碼
若是若是接收到的第二個參數不是空數組,那麼箭頭函數就是 componentDidMount
生命週期鉤子,而且在每次 name
狀態的值發生變化時做爲 componentDidUpdate
生命週期鉤子被觸發。dom
React.useEffect(() => {
// do something...
return () => {
console.log('返回一個箭頭函數,模擬 componentWillUnmount 生命週期鉤子')
}
}, [name])
複製代碼
若是在第一個參數裏返回箭頭函數,那麼返回的箭頭函數就是 componentWillUnmount
生命週期鉤子。
Refs Hook 的使用和 React.createRef()
的用法是同樣的。
export default function demo() {
// 建立一個 Ref 鉤子
const myRef = React.useRef()
function getText() {
alert(myRef.current.value)
}
return (
<div> <input type="text" ref={myRef} /><br/> </div>
)
}
複製代碼
組件懶加載能夠避免在第一次打開網頁時就把暫時尚未用到的組件都加載完畢,這樣能夠提升頁面加載速度,組件會在真正須要渲染時纔會被加載。
建立 A 組件:
export default class A extends Component {
render() {
return (
<div> <h2>我是 A</h2> </div>
)
}
}
複製代碼
建立 B 組件:
export default class B extends Component {
render() {
return (
<div> <h2>我是 B</h2> </div>
)
}
}
複製代碼
建立一個 Loading 組件,做用下面說:
export default class Loading extends Component {
render() {
return (
<div> <h2 style={{backgroundColor: 'orange'}}>Loding...</h2> </div>
)
}
}
複製代碼
import React, { Component, lazy, Suspense } from 'react'
import {NavLink, Route} from 'react-router-dom'
// 同步加載 Loading 組件
import Loading from './Loading'
// 懶加載 A 和 B
const A = lazy(() => import('./A'))
const B = lazy(() => import('./B'))
export default class Main extends Component {
render() {
return (
<div> <ul> <NavLink to="/a">Home</NavLink> <NavLink to="/b">About</NavLink> </ul> <hr/> <Suspense fallback={<Loading/>}> <Route path="/a" component={A} /> <Route path="/b" component={B} /> </Suspense> </div>
)
}
}
複製代碼
lazy()
函數用於實現組件的懶加載,接收一個函數提供要加載的組件,import()
導入函數和關鍵字 import
的做用是同樣的。須要注意的是使用了懶加載後就必需要使用 <Suspense>
組件對懶加載組件進行包裹,而且經過 fallback
屬性指定若是懶加載組件加載過程當中和加載失敗時的兜底顯示組件,也就是示例中的 Loading
組件,而且這個組件只能同步加載。
<Fragment>
實際是一個佔位組件,在編碼時看上去是一個組件,但在渲染時會被忽略。好比咱們一般在 render()
的返回內容最外層套一個 <div>
標籤,若是你不想渲染出來的結構有太多沒必要要的 <div>
,那麼就能夠用 <Fragment>
來替換。還有在遍歷渲染列表時,也能夠用來替代沒必要要的結構。
import React, { Component, Fragment } from 'react'
export default class FragmentDemo extends Component {
render() {
return (
<Fragment> <h2>Fragment 標籤在編譯時會被丟棄掉,能夠避免多組件時嵌套出太多層 div</h2> <h2>Fragment 能夠接收一個 key 屬性,這在遍歷的時候能夠用,避免產生太多外層元素</h2> </Fragment>
)
}
}
複製代碼
以前在建立類式組件時都是繼承 React.Component
,當多個組件嵌套而且子組件使用了父組件經過 props
傳遞過來的值時,若是父組件改變了 state
,不管子組件使用的那個狀態是否出現變更,子組件都會隨着父組件的渲染被從新渲染。
import React, { Component } from 'react'
export default class A extends Component {
state = {
name: 'zidu',
age: 27
}
changeName = () => {
this.setState({
name: 'CallbackZidu'
})
}
render() {
console.log('組件 A 發生渲染')
const {name, age} = this.state
return (
<div> <h2>組件 A</h2> <h2>名字:{name}</h2> <br/> <button onClick={this.changeName}>更名</button> <hr/> <B age={age}/> </div>
)
}
}
class B extends Component {
render() {
console.log('組件 B 發生渲染')
return (
<div> <h2>組件 B</h2> <h2>年齡:{this.props.age}</h2> </div>
)
}
}
複製代碼
很明顯這種子組件的被動渲染是沒有必要的,這時候就可讓子組件繼承 React.PureComponent
來避免這種狀況發生。React.PurComponent
實際重寫了 shouldComponentUpdate
生命週期鉤子,在內部判斷了 state
和 props
是否發生變更,以此決定是否容許組件執行狀態更新。爲了方便起見,全部組件均可以去繼承 React.PurComponent,而不用再繼承 React.Component。
名稱有點高端,看着有點懵逼,實際上就是和 Vue 的 Slot 同樣的插槽技術,只是語法不一樣而已。直接看示例:
import React, { PureComponent } from 'react'
import OtherComponent from '../OtherComponent'
export default class RenderPropsDemo extends PureComponent {
render() {
return (
<div> <h2>這是 RenderPropsDemo 組件</h2> {/* 經過傳入一個函數返回要插入的組件 */} <A render={() => <OtherComponent/>}/> </div>
)
}
}
/** * 使用 this.props.render() 在 JSX 中預留要插入其餘 DOM 的位置 * 相似 Vue 的 Slot 技術 * 函數名 render 是自定義的,改什麼名字均可以 */
class A extends PureComponent {
render() {
return (
<div> <h2>這是 A 組件</h2> {this.props.render()} </div>
)
}
}
複製代碼
在須要插入其餘組件的地方直接寫 this.props.render()
便可,以後插入的組件就會出如今這個位置。固然這個 props
上的 render
名字能夠隨便改,一般都叫這個。插入組件的時候直接在 props
上傳入一個箭頭函數,返回要插入組件的標籤便可。
父子組件之間通訊能夠走 props
,兄弟組件之間通訊能夠走發佈訂閱,那麼祖孫組件之間通訊就能夠用 Context,固然用發佈訂閱也能夠,並且更舒服。
好比如今 A 組件要把一些數據傳遞給 C 組件使用,那就可使用 React.createContext()
建立一個 Context。
import React, { Component } from 'react'
import './index.css'
// 建立一個 Context
const NameContext = React.createContext()
export default class ContextDemo extends Component {
state = {
name: 'zidu',
age: 27
}
render() {
const {name, age} = this.state
return (
<div className="parent"> <h2>A 組件</h2> <h2>名稱:{name}</h2> {/* 使用 Provider 包括下一級組件 */} <NameContext.Provider value={name}> <B /> </NameContext.Provider> </div>
)
}
}
class B extends Component {
render() {
return (
<div className="child"> <h2>B 組件</h2> <h2>顯示點東西打醬油</h2> <C/> </div>
)
}
}
function C() {
return (
<div className="grand"> <h2>C 組件</h2> <h2>使用 Consumer 標籤接收名稱: {/* Consumer 內的箭頭函數參數就是從 A 組件傳遞過來的數據 */} <NameContext.Consumer> { value => { return value.name } } </NameContext.Consumer> </h2> <D/> </div>
)
}
複製代碼
C 組件接收的時候也能夠不寫 <Consumer>
標籤,改用編程方式要簡單一些:
class C extends Component {
// 聲明要使用的 Context
static contextType = NameContext
render() {
return (
<div className="grand-grand"> <h2>C 組件</h2> <h2>使用 contextType 接收名稱:{this.context.name}</h2> </div>
)
}
}
複製代碼
Context 通常在封裝組件時使用,寫業務時較少用,瞭解一下就行。祖孫組件之間的通訊要麼走 Redux,要麼走發佈訂閱,用起來都比 Context 舒服。
子組件可能因爲請求數據失敗或者拿到的數據和約定的格式不符等狀況致使渲染失敗,這時候異常就會層層往外傳遞致使整個頁面渲染失敗出現錯誤信息。錯誤邊界就是將子組件的渲染錯誤限制在必定範圍內,並使用特定內容去替換錯誤信息。
好比在 A 組件裏使用了 B 組件,爲了不 B 出現渲染失敗出現上述狀況就能夠作以下措施:
state = {
childLoadSuccess: true
}
// 特定函數,記住就行
static getDerivedStateFromError(error) {
return {
childLoadSuccess: false
}
}
render() {
return (
<div> { this.state.childLoadSuccess ? <B/> : <h4>B組件發生了異常 </h4> } </div>
)
}
複製代碼
定義一個狀態 childLoadSuccess
用來標誌 B 組件是否渲染出錯,當 B 組件渲染出錯時觸發 getDerivedStateFromError
函數,在函數內部返回一個對象將 childLoadSuccess
改掉。這樣當 A 組件在渲染時就能夠經過 childLoadSuccess
的狀態決定渲染 B 組件仍是備用組件或信息。另外還有一個生命週期鉤子 componentDidCatch
也可使用,當出現異常時會被觸發,能夠在這裏向服務器提交錯誤報告。