创建React项目
npx create-react-app train-ticket --registry.npm.taobao.org
Context
- 创建Context实例的方法
Context提供一种方式,能够让数据在组件中不必一级一级手动传递
- Context使用示意图

- 使用Context和不使用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;
- 如果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
- import():动态导入,返回一个promise
import('./detail.js).then(...)
lazy的使用
- lazy需要传入一个没有参数的函数
- lazy的返回值就是一个React组件
//引入lazy
import React, { Component,lazy } from 'react'
const About = lazy(()=>import('./About.jsx'))
- lazy动态引入的内容会被单独打包成一个文件
Suspense的使用
- 由于用了lazy之后会存在一个加载中的空档,React不知道在这个空档改显示什么,所以需要使用Suspense指定
- 配合Lazy使用的Suspense,设置在加载中的时候显示的内容。
- 把异步导入的组件用Suspense包起来
- 设置Suspense的属性fallback,fallback中是一段JSX,也就是组件的实例,而不是组件的引用
// 引入Suspense
import React, { Component,lazy,Suspense } from 'react'
render(){
return (
<div>
//fallback中的参数是组件,而不是组件的引用
<Suspense fallback = {<div>loading</div>}>
<About/>
</Suspense>
</div>
)
}

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

- 如果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>
})