react新属性
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'));

- 处理加载异常
方法一,使用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源码