React Hooks

321 阅读5分钟

创建React项目

npx create-react-app train-ticket --registry.npm.taobao.org

Context

  1. 创建Context实例的方法

Context提供一种方式,能够让数据在组件中不必一级一级手动传递

  1. Context使用示意图
  2. 使用Context和不使用Context的对比

4. Context的使用

import React, { createContext, useState } from 'react';

const BatteryContext = createContext(90)
//如果没有Provider,Consumer并不会报错,这个时候创建Context时候的默认值会生效
//默认值的使用场景是Consumer找不到对应的Provider的时候,
const OnlineContext = createContext()

function Middle() {
  return (
    <Leaf />
  )
}

function Leaf() {
  return (
    <BatteryContext.Consumer>
      {
        battery => (
          <OnlineContext.Consumer>
            {
              online => (<h1>Battery:{battery},Oline:{String(online)}</h1>)
            }
          </OnlineContext.Consumer>
        )
      }
    </BatteryContext.Consumer>
  )
}

function App() {
  const [online, setOnline] = useState(false)
  const [battery, setBattery] = useState(60)
  return (
    <BatteryContext.Provider value={battery}>
      <OnlineContext.Provider value={online}>
        <button
          type='button'
          onClick={() => { setBattery(battery - 1) }}>
          Press
          </button>
        <button
          type='button'
          onClick={() => {
            setOnline(!online)
          }} >
          Switch
          </button>
        <Middle></Middle>
      </OnlineContext.Provider>
    </BatteryContext.Provider >
  )
}
export default App;

  1. 如果Consumer向上找不到对应的Provider会怎么样?
  • 结论是不会报错,只是Consumer找不到对应的值
createContext(defaultValue)
  • defaultValue的作用就是在Consumer找不到对应的Provider的时候取的值

ContextType

由于Context的Consumer用法写起来过于复杂,所以引入ContextType

class Leaf extends Component {
  static contextType = BatteryContext
  render() {
    const battery = this.context
    return (
      <h1>Battery:{battery}</h1>
    )
  }
}

contextType只能指向其中一个context

Lazy和Suspense

  1. import():动态导入,返回一个promise
import('./detail.js).then(...)

lazy的使用

  1. lazy需要传入一个没有参数的函数
  2. lazy的返回值就是一个React组件
//引入lazy
import React, { Component,lazy } from 'react'
const About = lazy(()=>import('./About.jsx'))
  • lazy动态引入的内容会被单独打包成一个文件

Suspense的使用

  1. 由于用了lazy之后会存在一个加载中的空档,React不知道在这个空档改显示什么,所以需要使用Suspense指定
  2. 配合Lazy使用的Suspense,设置在加载中的时候显示的内容。
  3. 把异步导入的组件用Suspense包起来
  4. 设置Suspense的属性fallback,fallback中是一段JSX,也就是组件的实例,而不是组件的引用
// 引入Suspense
import React, { Component,lazy,Suspense } from 'react'
render(){
    return (
      <div>
        //fallback中的参数是组件,而不是组件的引用
        <Suspense fallback = {<div>loading</div>}>
          <About/>
        </Suspense>
      </div>
    )
}

5. webpack在实现code spliting的同时支持自定义命名

const About = lazy(() => (
  import(/*webpackChunkName:'about'*/'./About.jsx')))

  1. 如果About加载失败怎么捕获错误,也就是说如果动态引入的组件或者静态引入的组件加载失败,这时候页面报错了,所以应该怎么处理,怎么捕获错误?

ErrorBoundary:可以捕获任何加载错误

class App extends Component {
  state = {
    hasError: false
  }
  static getDerivedStateFromError() {
    return {
      hasError: true
    }
  }
  render() {
    if (this.state.hasError) {
      return <div>error</div>
    }
    return (<div>
      <Suspense fallback={<div>loading</div>}>
        <About />
      </Suspense>
    </div>)
  }
}

或者

class App extends Component {
  state = {
    hasError: false
  }
  render() {
    if (this.state.hasError) {
      return <div>error</div>
    }
    return (<div>
      <Suspense fallback={<div>loading</div>}>
        <About />
      </Suspense>
    </div>)
  }
  componentDidCatch() {
    this.setState({
      hasError: true
    })
  }
}

ErrorBoundry可以捕获到任何渲染错误,那么怎么在错误发生之后重试一次呢?

Memo

shouldComponentUpdate

因为父组件执行render方法,子组件就会被重新渲染,而有的时候子组件的state或props没有发生变化,不需要重新渲染,这个时候可以使用shouldComponentUpdate进行任意层级的比较,来控制子组件是否要重新渲染。

export default class App extends Component {
  state = {
    count: 0
  }
  render() {
    const person = this.state.person
    return (<div>
      <button onClick={() => {
        this.setState({ count: this.state.count + 1})
      }}>Add</button>
      <Foo name='mike'></Foo>
    </div>)
  }
}
class Foo extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    //不再重新渲染
    if (nextProps.name === this.props.name) {
      return false
    }
    //重新渲染
    return true
  }
  render() {
    console.log('foo render')
    return null
  }
}

PuerCompoent

  • PureComponent提供的是:shouldComponentUpdate只比较props中的第一层数据
export default class App extends Component {
  state = {
    count: 0
  }
  render() {
    const count = this.state.count
    return (<div>
      <button onClick={() => {
        this.setState({ count: count+1 })

      }}>Add</button>
      <Foo name='mike'></Foo>
    </div>)
  }
}
class Foo extends PureComponent {
  render() {
    console.log('foo render')
    return null
  }
}
  • 局限性:只有传入属性本身的对比,如果属性的内部发生什么变化就搞不定了
  • 下面这个例子点击按钮,子组件不会重新渲染,所以一定要注意PureComponent的使用场景,只有传入的props的第一级发生变化,才会触发重新渲染
const Foo = memo(function Foo(props) {
  console.log('foo render')
  return <div>{props.person.age}</div>
})
export default class App extends Component {
  state = {
    count: 0,
    person: {
      age: 2
    }
  }
  //将callback改写成类属性
  callback = () => {
    //this的指向
  }
  render() {
    const person = this.state.person
    return (<div>
      <button onClick={() => {
        person.age++
        this.setState({ person: person })
      }}>Add</button>
      <Foo name='mike' person={person}></Foo>
    </div>)
  }
}

使用内联的回调函数,这个时候子组件会重新渲染,因为内联的函数每次都是一个新的

export default class App extends Component {
  state = {
    count: 0,
    person: {
      age: 2
    }
  }
  callback ()  {
    //this的指向
  }
  render() {
    const person = this.state.person
    return (<div>
      <button onClick={() => {
        person.age++
        this.setState({ person: person })
      }}>Add</button>
      //这两种方法都会创建新的函数,会引起不必要的渲染
      <Foo name='mike' person={person} cb={() => { }}></Foo>
      <Foo name='mike' person={person} cb={this.callback.bind(this)}></Foo>
    </div>)
  }
}
  • 将callback改写成类属性可以解决这个问题
export default class App extends Component {
  state = {
    count: 0,
    person: {
      age: 2
    }
  }
  callback= () => {
    //this的指向
  }
  render() {
    const person = this.state.person
    return (<div>
      <button onClick={() => {
        person.age++
        this.setState({ person: person })
      }}>Add</button>
      <Foo name='mike' person={person} cb={this.callback()}></Foo>
    </div>)
  }
}

Memo:将组件改写成函数组件形式,并用memo()包裹返回一个新组建,就可以用函数组件的形似实现一个和PureComponent相同的效果

const Foo = memo(function Foo(props) {
  console.log('foo render')
  return <div>{props.person.age}</div>
})

PuerComponent和Memo可以对比传入的值是否相等来决定是否重新渲染,那么这个相等是如何对比的呢

ErrorBoudary可以捕获到加载错误,那么怎么在错误加载之后重试一次呢?