1 context
2 contextType
3 lazy
4 suspense
5 memo
6 hooks
7 effect hooks
===========
1 Context
提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递 (但是不要滥用,因为它会破坏组件的复用性)
API: createContext(defaultValue)
示例1:基本用法
import React,{ Component,createContext } from 'react';// 在这里导出context函数
import logo from './logo.svg';
import './App.css';
const BatteryContext = createContext();// 在这里创建context
// 孙子组件
class Leaf extends Component {
render(){
// 在这里定义 consumer 消费者
return (
<BatteryContext .Consumer>
{
Battery => BatteryName:{Battery}
}
BatteryContext .Consumer>
)
}
}
// 子组件
class Middle extends Component{
render(){
return ;
}
}
// 父组件
class App extends Component {
render(){
// 在这里定义 Provider context的提供者
return (
<BatteryContext .Provider value = {60}>
BatteryContext .Provider>
)
}
}
export default App;
示例2: 动态改变其值:
import React,{ Component,createContext } from 'react';// 在这里导出context函数
import logo from './logo.svg';
import './App.css';
const BatteryContext = createContext();// 在这里创建context
// 孙子组件
class Leaf extends Component {
render(){
// 在这里定义 consumer 消费者
return (
{
Battery => BatteryName:{Battery}
}
)
}
}
// 子组件
class Middle extends Component{
render(){
return ;
}
}
// 父组件
class App extends Component {
state = {
battery: 50
}
render(){
const {battery} = state;
// 在这里定义 Provider context的提供者
return (
onclick={()=>{
this .setState({
battery: battery -1
})
}} />
)
}
}
export default App;
示例3:多个context嵌套
import React,{ Component,createContext } from 'react';// 在这里导出context函数
import logo from './logo.svg';
import './App.css';
const BatteryContext = createContext();// 在这里创建context
const OnlineContext = createContext();// 在这里创建context
// 孙子组件
class Leaf extends Component {
render(){
// 在这里定义 consumer 消费者
return (
{
battery => (
{
online => battery:{battery},online:{online}
}
)
}
)
}
}
// 子组件
class Middle extends Component{
render(){
return ;
}
}
// 父组件
class App extends Component {
state = {
battery: 50,
online: false ,
}
render(){
const {battery,online} = state;
// 在这里定义 Provider context的提供者
return (
onclick={()=>{
this .setState({
battery: battery -1
})
}} >
onclick={()=>{
this .setState({
online: !online
})
}} />
)
}
}
export default App;
示例4:使用static 化简
import React,{ Component,createContext } from 'react';// 在这里导出context函数
import logo from './logo.svg';
import './App.css';
const BatteryContext = createContext();// 在这里创建context
const OnlineContext = createContext();// 在这里创建context
// 孙子组件
class Leaf extends Component {
static contextType = BatteryContext;
render(){
const battery = this .context;
return (
battery:{battery}
)
}
}
// 子组件
class Middle extends Component{
render(){
return ;
}
}
// 父组件
class App extends Component {
state = {
battery: 50,
online: false ,
}
render(){
const {battery,online} = state;
// 在这里定义 Provider context的提供者
return (
onclick={()=>{
this .setState({
battery: battery -1
})
}} >
onclick={()=>{
this .setState({
online: !online
})
}} />
)
}
}
export default App;
2 lazy 加载
懒加载组件:
// app.js
import React,{ Component ,lazy ,Suspense} from 'react';
// lazy 的返回就是一个react组件
const About = lazy(()=> import('./About.jsx'));
// 父组件 必须用 Suspense 和lazy进行配合,fallback中加载一个实例
// 或者把 Loading
改为 组件
class App extends Component {
render(){
return (
Loading
}>
)
}
}
export default App;
子组件:
// About.js
import React,{ Component } from 'react';
// 子组件
export default class About extends Component {
render(){
return About
}
}
从 开发者工具中的 network 中可以看到:
如果想改变 trunk 的名字,将app父组件改为下面的方式:
// app.js
import React,{ Component ,lazy ,Suspense} from 'react';
// lazy 的返回就是一个react组件
const About = lazy(()=> import(/* webpackChunkName:"about" */ './About.jsx'));
则:
如果子组件加载失败,增加容错机制:
// app.js
import React,{ Component ,lazy ,Suspense} from 'react';
// lazy 的返回就是一个react组件
const About = lazy(()=> import(/* webpackChunkName:"about" */ './About.jsx'));
// ErrorBoundary 错误边界
// componentDidCatch 生命周期函数 如果 render() 函数抛出错误,则会触发该函数。
// 错误在渲染阶段中被捕获,但在事件处理程序中不会被捕获
class App extends Component {
state = {
hasError: false
};
componentDidCatch(){
this .setState({
hasError: true
})
}
render(){
if (this .state.hasError){
return error
}
return (
Loading
}>
)
}
}
export default App;
3。memo
先看一个示例:父组件每次更新state中的 count,其子组件 Foo 都会执行;
// App.jsx
import React,{ Component } from 'react';
// 子组件
class Foo extends Component {
render(){
console.log( 'foo render');
return null ;
}
}
// 父组件
class App extends Component {
state ={
count: 0,
}
render(){
return (
{
this .setState({
count: this .state.count++
})
}} >
)
}
}
export default App;
优化一:使用生命周期函数 shouldComponentUpdate:
子组件改为:
// 子组件
class Foo extends Component {
shouldComponentUpdate(nextProps,nextState){
if (nextProps.name === this .props.name){
return false ;
}
return true ;
}
render(){
console.log( 'foo render');
return null ;
}
}
优化二: 使用 PureComponent ,子组件改为:
// App.jsx
import React,{ Component,PureComponent } from 'react';
// 子组件
class Foo extends PureComponent {
render(){
console.log( 'foo render');
return null ;
}
}
但是有局限性,只能对传入的属性做简单的对比,如果属性内部发生变化,则不会监听到,导致子组件不会改动:
// App.jsx
import React,{ Component,PureComponent } from 'react';
// 子组件
class Foo extends PureComponent {
render(){
console.log( 'foo render');
return {this .props.person.age}
;
}
}
// 父组件
class App extends Component {
state ={
count: 0,
person:{
age: 1,
}
}
render(){
const person = this .state.person;
return (
{
person.age ++
this .setState({
person
})
}} >
)
}
}
export default App;
如上所示:改变了父组件中state的 person对象中的age属性,但是子组件是无法监听到的,只能监听到第一级的数据;
另一个局限:
父组件给子组件有个内联函数: {}}/> 的时候,每次改变父组件的state值,都会创建一个新的函数对象。子组件都会被重新渲染;
类似的 this .callback.bind(this )}/> ,子组件也会被重新渲染。
改为下面的方法,即可以:
// 父组件
class App extends Component {
state ={
count: 0,
person:{
age: 1,
}
}
callback =()=>{
}
render(){
const person = this .state.person;
return (
this.callback}/>
)
}
}
export default App;
将子组件改为无状态函数后,每次改变state 子组件也会改变:
// App.jsx
import React,{ Component,PureComponent } from 'react';
// 子组件
function Foo(props) {
render(){
console.log( 'foo render');
return {props.person.age}
;
}
}
// 父组件
class App extends Component {
state ={
count: 0,
person:{
age: 1,
}
}
callback =()=>{
}
render(){
const person = this .state.person;
return (
this.callback}/>
)
}
}
export default App;
但是这样的话,每次子组件都会被渲染,这时候 memo 出现了:它相当于 class 中的 PureComponent:
// App.jsx
import React,{ Component,PureComponent ,memo } from 'react';
// 子组件
const Foo = memo (
function Foo(props) {
render(){
console.log( 'foo render');
return {props.person.age}
;
}
}
)
// 父组件
class App extends Component {
state ={
count: 0,
person:{
age: 1,
}
}
callback =()=>{
}
render(){
const person = this .state.person;
return (
this.callback}/>
)
}
}
export default App;
这样,拆分的组件传入的属性越简单,越容易写成上述方式;
6 hooks
类组件不足:
1 状态逻辑复用难:缺少复用机制,渲染属性和高阶组件导致层级冗余;
2 趋向于复杂难以维护: 生命周期函数混杂不相干逻辑,相干逻辑分散在不同生命周期
3 this 指向困扰:内联函数过度创建新句柄,累成员函数不能保证this;
(注意:点击组件内的按钮,state中的count发生变化,组件将渲染,但设置默认state只在第一次时渲染)
// react 分为 聪明组件和傻瓜组件 按我的理解 这个应该用在傻瓜组件中
// 父组件中 state的变化 导致子组件中 props的变化 导致子组件的重新渲染
function App(){
const [count,setCount] = useState(0);
const [name,setName] = useState('like');
return (
onClick = {()=>{setCount(count++)}}
>
{count}
{name}
)
}
问题一:useState设置的默认值,如何知道对应的给state?
答: useState本身只是单纯的设置一个值,然后是结构赋值功能,赋给了对应的state;
问题二:每个组件都有useState,它是如何保证只赋值给当前组件中的count,而不是其他组件的count呢?
答: 利用了js的单线程原理,当前只能赋值给当前作用域下的组件。
问题三: 如果一个组件有多个useState, 每次渲染该组件的时候,是如何返给定义的state 呢?
答:useState是根据第一次渲染执行组件的时候,定义的state顺序赋值的,所以不能改变赋值的顺序。例如下面的示例:
let id = 0;
function App(){
let name,setName;
let count,setCount;
id ++;
if (id & 1){// 如果id是奇数
[count,setCount] = useState(0);
[name,setName] = useState('like');
} else {
[name,setName] = useState('like');
[count,setCount] = useState(0);
}
return (
onClick = {()=>{setCount(count++)}}
>
{count}
{name}
)
}
下面的形式也是不行的:
let id = 0;
function App(){
const [count,setCount] = useState(0);
const [name,setName] = useState('like');
id ++;
if (id>1){
useState( 'dd'); // 从第二次渲染之后 增加的的设置
}
return (
onClick = {()=>{setCount(count++)}}
>
{count}
{name}
)
}
下面的形式也不行:
let id = 0;
function App(){
const [count,setCount] = useState(0);
const [name,setName] = useState('like');
id ++;
if (id===1){
useState( 'dd'); // 只在第一次渲染时 增加的的设置
}
return (
onClick = {()=>{setCount(count++)}}
>
{count}
{name}
)
}
比如说 state的默认值是基于 props 的:注意:点击组件内的按钮,state中的count发生变化,组件将渲染,但设置默认state只在第一次时渲染
function App(){
const [count,setCount] = useState(()=>{
return props.defaultCount || 0; // 只会执行一次
});
const [name,setName] = useState('like');
return (
onClick = {()=>{setCount(count++)}}
>
{count}
{name}
)
}
7 effect hooks 副作用时机
原来的生命周期:
Mount之后: componentDidMount
update 之后 componentDidUpdate
Unmount 之前 componentWillUnmount
用 useEffect 函数来替换;
userEffect函数是在 render之后调用的 ,其功能相当于 componentDidMount/和componentDidUpdate,并且该函数有callback函数,其功能是清除上一次副作用 遗留下来的状态 相当于componentWillUnmount
示例1: 点击按钮 文本中和页面title均发生变化,使用原来的生命周期开发:
export default class App extends Component {
state = {
count: 0
}
componentDidMount(){
doucument.title = this .state.count;
}
componentDidUpdate(){
doucument.title = this .state.count;
}
render(){
const { count } = this .state;
return (
onClick={()=>{
this .setState({
count:count ++
})
}} >
{count}
)
}
}
可见,为了实现初始化时和数据更新时,title发生变化,同样的交互代码要在两个生命周期中执行两次,类似的 再加上 监听函数,需要在卸载生命周期中 去掉卸载函数:
export default class App extends Component {
state = {
count: 0,
size:{
with :doucument.doucumentElement.clientWidth,
height:doucument.doucumentElement.clientHeight
}
}
componentDidMount(){
doucument.title = this .state.count;
window.addEvevntListener ( 'resize',this .onResize,false );
}
componentwillUnMount (){
window.removeEventListner ( 'resize',this .onResize,false );
}
onResize = ()=>{
this .setState({
size:{
with :doucument.doucumentElement.clientWidth,
height:doucument.doucumentElement.clientHeight
}
})
}
componentDidUpdate(){
doucument.title = this .state.count;
}
render(){
const { count,size } = this .state;
return (
onClick={()=>{
this .setState({
count:count ++
})
}} >
{count}
size:{size.width}X{size.height}
)
}
}
现在使用 effect hooks:
//1 提高了代码复用(合并多个生命周期成了一个函数) //2 优化了关注点分离,即不同的事件放在了不同的 useEffect 函数中
function App(){
// 定义初始化数据
const [count,setCount] = useState(0);
const [size,setSize] = useState({
with :doucument.doucumentElement.clientWidth,
height:doucument.doucumentElement.clientHeight
});
// 常规函数
const onResize = ()=>{
setState({
with :doucument.doucumentElement.clientWidth,
height:doucument.doucumentElement.clientHeight
})
}
// 1 提高了代码复用(合并多个生命周期成了一个函数)
// 2 优化了关注点分离,即不同的事件放在了不同的 useEffect 函数中
// 使用副作用在某些生命周期中执行数据的操作
useEffect(()=>{
doucument.title = count;
})
useEffect(() =>{
window.addEvevntListener( 'resize',onResize,false );
return ()=>{ // 默认是组件重渲染和组件卸载的时候执行
window.addEvevntListener('resize',onResize,false );
}
},[]);
// 上面useEffect函数的空数组的参数,其作用是用于比对。决定该 useEffect 是否执行
// 如果第二个参数不写,则每次都会执行这个 useEffect ,如果为空数组,则只执行一次
// 如果数组中写了数据,则比对每一个数据,只有数组中的每一项都不变的情况下,才会再次执行;
// 如下面,变化size 不会触发下面useEffect的函数执行
useEffect(()=>{
console.log(count);
},[count])
return (
{setCount(count++)}}>
{count}
size:{size.width}X{size:height}
)
}
export default App;
8 hooks 环境下的的context
由前面 context知识可以知道 ContextType 只能存在于 Class中,则hook是的无状态函数咋整?
下面的示例给出了使用context的三个方法:
import React,{ Component, useState, createContext, useContext} from 'react';
const CountContext = createContext();// 在这理定义context的外层组件
// 子组件(最基础的写法)
class Foo extends Component{
render(){
return (
{
count => {count}
}
)
}
}
// 子组件(优化的写法)适用于类组件
class Bar extends Component{
static contextType = CountContext;
render(){
const { count} = this .state;
return (
{count}
)
}
}
// hooks中使用 context 可以获取多个 context
function Counter(){
const count = useContext(CountContext);
return (
{count}
)
}
// 父组件
export default class App extends Component {
const [ count,setCount] = useState(0);
render(){
return (
onClick={()=>{setCount(count+1)}}
>
click({count})
)
}
}
9 hooks中的 useMemo 函数
不同点:
useMemo函数是在渲染过程中执行,同比 useEffect是在渲染后执行;
useMemo函数有返回值,同比 useEffect 没有返回值;
相同点:
useMemo 函数和 useEffect 函数均有第二个参数,决定是否执行该函数。
示例:
import React,{ Component, useState, useMemo} from 'react';
function Counter(props){
return (
{props.count}
)
}
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3])
return (
{setCount(count++)}} >
click:({count}), double :({double })
)
}
export default App;
如上所示,当 count==3的时候,useMemo中数组的值由 false变为true, double 发生变化
当 count ==4 的时候, useMemo 中数组的值。由true 变为 false,double 再次发生变化;
import React,{ Component, useState, useMemo} from 'react';
function Counter(props){
return (
{props.count}
)
}
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3]);
// 还可以依赖 memo
const half = useMemo(()=>{
return double/4;
},[double]);
return (
{setCount(count++)}} >
click:({count}), double :({double })
)
}
export default App;
10 hooks中的 callback 函数
首先看一下memo函数,用memo包裹Counter函数,只有count发生变化的时候,才执行Count函数;
import React,{ Component, useState, useMemo, memo} from 'react';
const Counter = memo(function Counter(props){
cosole.log( 'count 发生变化的时候才执行');
return (
{props.count}
)
})
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3]);
return (
{setCount(count++)}} >
click:({count}), double :({double })
double}/>
)
}
这时给 子组件 Counte 增加 回调函数 onclick
import React,{ Component, useState, useMemo, memo} from 'react';
const Counter = memo(function Counter(props){
cosole.log( 'count 发生变化的时候才执行');
return (
{props.count} //这里
)
})
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3]);
const onClick = ()=>{
console.log( 'click me'); //父组件中定义回调函数
}
return (
{setCount(count++)}} >
click:({count}), double :({double })
double} onClick={onClick}/> //监听的函数
)
}
export default App;
由于回调函数 onclick的存在,每次父组件中app的变化,都 会导致子组件发生渲染;所以可以在父组件中使用 memo
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3]);
const onClick = useMemo(()=>{
return ()=>{
console.log( 'click me');
}
},[]); // 改变了这里
return (
{setCount(count++)}} >
click:({count}), double :({double })
double} onClick={onClick}/>
)
}
然后使用 useCallback 化简:
function App(){
const [count,setCount] = useState(0);
const double = useMemo(()=>{
return count*2;
},[count === 3]);
const onClick = useCallback(()=>{
console.log( 'click me');
},[]); // 改变了这里
// useMemo(()=>fn);
// useCallback(fn); useCallback 相当于是简化写法
return (
{setCount(count++)}} >
click:({count}), double :({double })
double} onClick={onClick}/>
)
}
这样,就不会因为父组件中的 回调函数 onClick的变化导致子组件发生变化:
1 子组件中使用 memo函数可以避免重复渲染,而是根据传入的props发生变化时才渲染; 2 父组件中使用 useMemo函数可以避免因为 回调函数的存在,导致子组件的渲染;
11 hooks中的 useRef
获取子组件或者DOM节点的句柄
渲染周期之间共享数据的存储
示例1:
import React,{ Component, PureComponent,useRef} from 'react';// 这里引入 useRef组件
class Counter extends PureComponent {
speak(){
console.log( 'speak');
}
render(){
const { props } = this ;
return (
{props.count} )
}
}
function App(){
const [count,setCount] = useState(0);
const counterRef = useRef();// 创建一个ref,在组件中使用该counrerRef
const onClick = useCallback(()=>{
counterRef.current.speak(); // 执行子组件中的speak函数,current属性 获取最终的值
},[counterRef]);
return (
{setCount(count++)}} >
click:({count})
ref={counterRef} count={double } onClick={onClick}/>
)
}
export default App;
示例2: 假设组件中定义一个变量,每秒加1,要求大于10之后不再增加;
function App(){
const [count,setCount] = useState(0);
const counterRef = useRef();
let it; // 因为更新state,会导致app组件重新渲染,it会重新初始化,而state只在第一次初始化,但是也不便于将it
// 放在state中,毕竟它没有用于渲染组件
useEffect(()=>{
it = setInterval(()=>{
setCount(count =>count+1)
}, 1000)
},[]);
useEffect(() =>{
if (count >= 10){
clearInterval(it);
}
})
const onClick = useCallback(()=>{
counterRef.current.speak();
},[counterRef]);
return (
{setCount(count++)}} >
click:({count})
double} onClick={onClick}/>
)
}
使用ref,将it改为类似于类的结构成员变量:
import React,{ Component, PureComponent,useEffect,useRef} from 'react';// 这里引入 useRef组件
class Counter extends PureComponent {
speak(){
console.log( 'speak');
}
render(){
const { props } = this ;
return (
{props.count}
}
}
function App(){
const [count,setCount] = useState(0);
const counterRef = useRef();
let it = useRef();// 改变了这里
useEffect(()=>{
it.current = setInterval(()=>{ // 改变了这里 it.current
setCount(count=>count+1)
}, 1000)
},[]);
useEffect(() =>{
if (count >= 10){
clearInterval(it.current); // 改变了这里 it.current
}
})
const onClick = useCallback(()=>{
counterRef.current.speak();
},[counterRef]);
return (
{setCount(count++)}} >
click:({count})
double} onClick={onClick}/>
)
}
export default App;
最后:自定义hooks
funciton useCount(defaultCount){
const [count,setCount] = useState(defaultCount);
const it = useRef();
useEffect(() =>{
it.current = setInterval(()=>{
setCount(count =>count +1 );
}, 1000)
},[]);
useEffect(() =>{
if (count >= 10){
clearInterval(it.current);
}
});
return [count,setCount]
}
示例2 :
import React,{ Component, PureComponent,useEffect,useRef} from 'react';// 这里引入 useRef组件
function useCounter(count) {
const size = useSize();// 共用 useSize函数1
return (
{count},{size.width},{size.height}
)
}
function useSize(){
const [size,setSize] = useSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeigth
})
const onResize = useCallback(()=>{
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeigth
})
},[]);
useEffect(() =>{
window.addEventListener( 'resize',onResize,false );
return ()=>{
window.removeEventListener( 'resize',onResize,false );
}
},[])
}
funciton useCount(defaultCount){
const [count,setCount] = useState(defaultCount);
const it = useRef();
useEffect(() =>{
it.current = setInterval(()=>{
setCount(count =>count +1 );
}, 1000)
},[]);
useEffect(() =>{
if (count >= 10){
clearInterval(it.current);
}
});
return [count,setCount]
}
function App(){
const [count,setCount] = useCount(0); // 这里也是自定义的hooks组件
const Counter = useCounter(count); // 这里调用的是自定义的hooks函数useCounter
const size = useSize(); // 共用 useSize函数2
return (
{setCount(count++)}} >
click:({count}), {count},{size.width},{size.height}
{Counter} // 这里调用的 上面 useCounter(count)
)
}
export default App;
最后注意:
一般hooks 都是由 use 为前缀的,一定要遵循:
1. 把hook 放在最顶层,不要放在条件语句中,因为它依赖顺序;
2. 仅在组件和自定义hooks组件中调用,不要在其他普通函数中调用,因为普通函数说不清在哪里会被调用,导致hooks的顺序变化,例如
function useLogin (){ // 自定义hooks,在其他地方调用也会是在顶层 有顺序的
const [login.setLogin] = useState();
useEffect(() =>{
})
}
function fetchNews(){// 而普通函数,说不清在哪里被调用,有肯能导致顺序不一样
const [pageNo,setPageNo] = useState();
}
===============分割线
Hooks 常见问题:
对传统react 编程的影响
1 生命周期函数如何映射到hooks
function App(){
useEffect(() =>{
// componentDidMount
return ()=>{
// componentWillUnmount
}
},[]); // 第二个参数为空数组,则只执行一次。挂载时执行一次,卸载时执行一次。
let renderCounter = useRef(0);
renderCounter.current ++;
useEffect(() =>{
if (renderCounter >1){
// componentDidUpdate
}
}) // 没有第二个参数,则每次都执行
}
2 类实例成员变量如何映射到hooks?
答:使用 useRef
3 Hooks 中如何获取历史props和state
function Counter(){
const [count,setCount] = useState(0);
const preCountRef = useRef();// useRef不会受组件重新渲染的影响,保留上一次的值,所以定义了ref的值 preCountRef
useEffect(() =>{
preCountRef.current = count; // 没有第二个参数,表示每次都执行。则update时将count赋值给ref
})
const prevCount = prevCountRef.current;
return now:{count},before:{prevCount}
}
4 如何强制更新一个Hooks组件?
思路:设置一个没有参与渲染的data,然后改变它的值:
function Counter(){
const [count,setCount] = useState(0);
const [updater,setUpdater] = useState(0);
function forceUpdate(){
setUpdater(updater => updater+1);// 组件没有用到这个data,强制执行该函数,则更新渲染组件
}
const preCountRef = useRef();
useEffect(() =>{
preCountRef.current = count;
})
const prevCount = prevCountRef.current;
return now:{count},before:{prevCount}
}