Context
Context提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递。
但这种类似全局变量的方式,会让组件失去独立性,复用起来比较麻烦。
API
- createContext(defaultValue?)
使用
- 引用
import React, { createContext } from 'React';
- 声明一个Context
注意一定要大写开头,因为会当做一个组件来使用。
const TestContext = createContext(0);
- 在父组件中使用TestContext组件,且定义为生产者Provider,用value属性传值。
<TestContext.Provider value={10}>
</TestContext.Provider>
- 在子组件中使用TestContext组件,且定义为消费者Consumer,通过箭头函数来获取传过来的值。
Context.Consumer
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的value值等等价于组件树上方离这个 context 最近的 Provider 提供的value值。如果没有对应的 Provider,value参数等同于传递给createContext()的defaultValue。
<TestContext.Consumer>
{
test => <h1>test:{test}</h1>
}
</TestContext.Consumer>
来一个完整demo代码:
有三层组件嵌套:
父App -> 子Middle -> 孙Leaf。
在父App组件中,把state里的test值,通过Context的生产者传给所有子元素。
在孙Leaf组件中,通过Context的消费者拿到了从祖先元素传过来的test值。且每一次在祖先元素的修改,孙组件的视图也会刷新。
App.jsx:
import React, { createContext, useState } from 'react';
import './App.css';
const TestContext = createContext(0);
function Middle() {
return (
<div>
<Leaf></Leaf>
</div>
)
}
function Leaf() {
return (
<div>
<TestContext.Consumer>
{
test => <h1>test:{test}</h1>
}
</TestContext.Consumer>
</div>
)
}
function App() {
const [test, setTest] = useState(0);
return (
<div className="App">
<TestContext.Provider value={test}>
<Middle/>
</TestContext.Provider>
<button onClick={ () => { setTest(test + 1) } }>点击我给test的值加1</button>
</div>
);
}
export default App;
Class.contextType
背景
由于Consumer的特性,里面的JSX语句,必须是函数的返回值,这样的代码会有累赘,所以contextType登场了~
作用
静态属性contextType访问跨层级组件的数据。
使用
- 父组件要传递过来的数据要通过Context.Provider提供,此处省略代码
- contextType是一个类静态变量,所以使用static关键字来声明它
static contextType = TestContext;
- 经过步骤2的声明后,在
render函数里,我们就可以通过this.context来获取到父组件传递过来的值(实际为test),为方便后续使用,赋值给变量test
const test = this.context;
- 要用直接引用test即可,不需要再用Consumer以及它的返回函数
class Leaf extends Component {
static contextType = TestContext;
render() {
const test = this.context;
return (
<div>
<h1>test:{test}</h1>
</div>
)
}
}
完整项目代码
App.jsx
import React, { Component, createContext } from 'react';
import './App.css';
const TestContext = createContext(0);
class Middle extends Component {
render() {
return <Leaf />
}
}
class Leaf extends Component {
static contextType = TestContext;
render() {
const test = this.context;
return (
<div>
<h1>test:{test}</h1>
</div>
)
}
}
class App extends Component {
state = {
test: 0,
};
render() {
const { test } = this.state;
return (
<div className="App">
<TestContext.Provider value={test}>
<Middle/>
</TestContext.Provider>
<button onClick={ () => { this.setState({test: test + 1}) } }>点击我给test的值加1</button>
</div>
)
}
}
export default App;
lazy 和 Suspense实现延迟加载(懒加载)
注意:这封装的是组件的导入行为,而不是组件本身
使用
- 引用lazy
import React, { Component, lazy } from 'react';
- 使用lazy函数来封装导入组件
- lazy()函数需要传入一个没有参数的函数
- 没有参数的函数内部直接使用import来导入组件
- lazy()函数的返回就是一个react组件
const About = lazy(() => import('./components/About'));
- 引入suspense
import React, { Component, lazy, Suspense } from 'react';
- 给需要异步加载的组件外包裹上
Suspense,且设置Suspense组件的fallback属性
fallback属性:
设置加载过程中需要显示的信息。
但需要注意要传入的是组件实例,如<Loading/>,而不能为组件名,如Loading
class App extends Component {
render() {
return (
<div>
<Suspense fallback={<div>Loading</div>}>
<About/>
</Suspense>
</div>
)
}
}
- 可以修改这chunk的名字
可以在控制台的NetWork中看到,这模块是异步加载的。所以可以通过webpack语法来修改这个chunk的名字。
const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));
image.png
- 处理加载异常
方法一,使用componentDidCatch()生命周期函数
import React, { Component, lazy, Suspense } from 'react';
const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));
class App extends Component {
state = {
hasError: false
};
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
this.setState({
hasError: true
})
}
render() {
const { hasError } = this.state;
if (hasError) {
return <h1>有错误!</h1>
}
return (
<div>
<Suspense fallback={<div>Loading</div>}>
<About/>
</Suspense>
</div>
)
}
}
export default App;
方法二,使用静态方法getDerivedStateFromError(),该方法的返回值会修改state
import React, { Component, lazy, Suspense } from 'react';
const About = lazy(() => import(/* webpackChunkName: "about" */ './components/About'));
class App extends Component {
state = {
hasError: false
};
static getDerivedStateFromError() {
return {
hasError: true
}
}
render() {
const { hasError } = this.state;
if (hasError) {
return <h1>有错误!</h1>
}
return (
<div>
<Suspense fallback={<div>Loading</div>}>
<About/>
</Suspense>
</div>
)
}
}
export default App;
memo
背景
当我们在父App组件中,引入了子Foo组件时,但父组件的某些属性发生了改变,App的视图会刷新,而哪怕属性值与Foo子组件无关,子组件也会跟随刷新,这不是我们想要的,消耗了性能。
Class的生命周期函数shouldComponentUpdate
通过shouldComponentUpdate(nextProps, nextState, nextContext),我们可以在将要刷新之前拿到组件的相关数据,然后再函数里边通过对比,如果不想要刷新视图的场景,返回false即可。
import React, { Component } from 'react';
class Foo extends Component {
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (nextProps.name === this.props.name) {
return false;
}
return true;
}
render() {
console.log('Foo~')
return null;
}
}
class App extends Component {
state = {
count: 0
};
render() {
const { count } = this.state;
return (
<div>
<h1>{ count }</h1>
<button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
<Foo name="foo"/>
</div>
)
}
}
export default App;
PureComponent
PureComponent其实就相当于帮我们实现了一个shouldComponentUpdate(~)函数,但有局限性,只能判断最外层的属性值,不可以判断对象中的属性或者函数。
import React, { Component, PureComponent } from 'react';
class Foo extends PureComponent {
render() {
console.log('Foo~')
return null;
}
}
class App extends Component {
state = {
count: 0
};
render() {
const { count } = this.state;
return (
<div>
<h1>{ count }</h1>
<button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
<Foo name="foo"/>
</div>
)
}
}
export default App;
Memo用法
无状态组件可以通过函数的形式来定义。
那函数组件就无法继承PureComponent了,而memo,就相当于是PureComponent,可用于函数声明的组件。
- 引入memo
import React, { Component, memo } from 'react';
- 使用memo函数包裹函数组件
const Foo = memo(function Foo() {
console.log('Foo~')
return null;
})
完整代码
import React, { Component, memo } from 'react';
const Foo = memo(function Foo() {
console.log('Foo~')
return null;
})
class App extends Component {
state = {
count: 0
};
render() {
const { count } = this.state;
return (
<div>
<h1>{ count }</h1>
<button onClick={() => this.setState({count: count + 1 })}>点我加一</button>
<Foo name="foo"/>
</div>
)
}
}
export default App;
那么像PureComponent、memo是用什么算法来判断是否需要重新计算和执行?是使用===吗?还是其他?
答案:使用的是shallowEqual 算法。
shallowEqual 算法在简单类型上使用了Object.is,但是对于引用类型,shallowEqual会做多一层的比较,比如两个对象{a: 1}和{a: 1},用Object.is判断肯定是不相等的,但是对于shallowEqual,它们则是等价的,对于数组的判断也类似。
参考:https://coding.imooc.com/learn/questiondetail/121369.html
补充:shallowEqual.js源码













网友评论