React 一些知道最好不知道也行的點

setState 的用法

setState() 函數用於更新組件的狀態,有對象式和函數式兩種寫法。javascript

// 對象式
this.setState({
  count: this.state.count + 1
})
// 函數式
this.setState((state, props) => {
  count: state.count + 1
})
複製代碼

函數式寫法能夠直接從參數接收到當前組件的 stateprops,更方便對狀態進行處理。不管哪一種寫法,setState() 的第二個參數均可以接收一個回調函數。css

this.setState({
  count: this.state.count + 1
}, () => { // 回調函數
  console.log(this.state.count)
})
複製代碼

setState() 對狀態的更新是異步的,回調函數會在狀態變動完成後觸發。也就是說若是直接在 setState() 調用以後去輸出一個狀態值,那麼頗有可能拿到的仍是變動以前的值。java

Hooks

Hooks 是 16.8 版本開始推出的針對函數式組件的功能。函數式組件沒有 this,因此沒法操做 staterefs 和生命週期鉤子,而 Hooks 解決了這些問題。實際使用來看,並無類組件寫着舒服,因此仍是老實用類組件吧!react

State Hook

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

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

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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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

<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>
    )
  }
}
複製代碼

PureComponent

以前在建立類式組件時都是繼承 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 生命週期鉤子,在內部判斷了 stateprops 是否發生變更,以此決定是否容許組件執行狀態更新。爲了方便起見,全部組件均可以去繼承 React.PurComponent,而不用再繼承 React.Component

RenderProps

名稱有點高端,看着有點懵逼,實際上就是和 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 上傳入一個箭頭函數,返回要插入組件的標籤便可。

Context

父子組件之間通訊能夠走 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 也可使用,當出現異常時會被觸發,能夠在這裏向服務器提交錯誤報告。

相關文章
相關標籤/搜索