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 ({ Battery => ) }}//子组件class Middle extends Component{ render(){ returnBatteryName:{Battery}
}; }}//父组件class App extends Component { render(){ //在这里定义 Provider context的提供者 return ( ) }}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 => ) }}//子组件class Middle extends Component{ render(){ returnBatteryName:{Battery}
}; }}//父组件class App extends Component { state = { battery:50 } render(){ const {battery} = state; //在这里定义 Provider context的提供者 return (
示例3:多个context嵌套
import React,{ Component,createContext } from 'react';//在这里导出context函数import logo from './logo.svg';import './App.css';const BatteryContext = createContext();//在这里创建contextconst OnlineContext = createContext();//在这里创建context//孙子组件class Leaf extends Component { render(){ //在这里定义 consumer 消费者 return ({ battery => ( ) }}//子组件class Middle extends Component{ render(){ return{ online => ) }battery:{battery},online:{online}
}; }}//父组件class App extends Component { state = { battery:50, online:false, } render(){ const {battery,online} = state; //在这里定义 Provider context的提供者 return ( ) }}export default App;
示例4:使用static 化简
import React,{ Component,createContext } from 'react';//在这里导出context函数import logo from './logo.svg';import './App.css';const BatteryContext = createContext();//在这里创建contextconst 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 ( ) }}export default App;
2 lazy 加载
懒加载组件:
// app.jsimport 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.jsimport React,{ Component } from 'react';//子组件export default class About extends Component { render(){ returnAbout}}
从 开发者工具中的 network 中可以看到:
如果想改变 trunk 的名字,将app父组件改为下面的方式:
// app.jsimport React,{ Component ,lazy ,Suspense} from 'react';//lazy 的返回就是一个react组件const About = lazy(()=> import(/* webpackChunkName:"about"*/'./About.jsx'));
则:
如果子组件加载失败,增加容错机制:
// app.jsimport 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){ returnerror} return (}>Loading ) }}export default App;
3。memo
先看一个示例:父组件每次更新state中的 count,其子组件 Foo 都会执行;
// App.jsximport React,{ Component } from 'react';//子组件class Foo extends Component { render(){ console.log('foo render'); return null; }}//父组件class App extends Component { state={ count:0, } render(){ return () }}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.jsximport React,{ Component,PureComponent } from 'react';//子组件class Foo extends PureComponent { render(){ console.log('foo render'); return null; }}
但是有局限性,只能对传入的属性做简单的对比,如果属性内部发生变化,则不会监听到,导致子组件不会改动:
// App.jsximport 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 () }}export default App;
如上所示:改变了父组件中state的 person对象中的age属性,但是子组件是无法监听到的,只能监听到第一级的数据;
另一个局限:
父组件给子组件有个内联函数: <Foo person={person} cd={()=>{}}/> 的时候,每次改变父组件的state值,都会创建一个新的函数对象。子组件都会被重新渲染;
类似的 <Foo person={person} cd={ this.callback.bind(this)}/> ,子组件也会被重新渲染。
改为下面的方法,即可以:
//父组件class App extends Component { state={ count:0, person:{ age:1, } } callback=()=>{ } render(){ const person = this.state.person; return () }}export default App;
将子组件改为无状态函数后,每次改变state 子组件也会改变:
// App.jsximport 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 () }}export default App;
但是这样的话,每次子组件都会被渲染,这时候 memo 出现了:它相当于 class 中的 PureComponent:
// App.jsximport 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 () }}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 ( )}
问题一: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 ( )}
下面的形式也是不行的:
let id = 0;function App(){ const [count,setCount] = useState(0); const [name,setName] = useState('like'); id++; if(id>1){ useState('dd'); //从第二次渲染之后 增加的的设置 } return ( )}
下面的形式也不行:
let id = 0;function App(){ const [count,setCount] = useState(0); const [name,setName] = useState('like'); id++; if(id===1){ useState('dd'); //只在第一次渲染时 增加的的设置 } return ( )}
比如说 state的默认值是基于 props 的:注意:点击组件内的按钮,state中的count发生变化,组件将渲染,但设置默认state只在第一次时渲染
function App(){ const [count,setCount] = useState(()=>{ return props.defaultCount || 0; //只会执行一次 }); const [name,setName] = useState('like'); return ( )}
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 ( ) }}
可见,为了实现初始化时和数据更新时,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 ( ) }}
现在使用 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 ( )}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 => ) }}//子组件(优化的写法)适用于类组件class Bar extends Component{ static contextType = CountContext; render(){ const { count} = this.state; return ({count}
}{count}
) }}//hooks中使用 context 可以获取多个 contextfunction Counter(){ const count = useContext(CountContext); return ({count}
)}//父组件export default class App extends Component { const [ count,setCount] = useState(0); render(){ return () }}
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 ()}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 ()}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 ()}
这时给 子组件 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 ()}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 ()}
然后使用 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 ()}
这样,就不会因为父组件中的 回调函数 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 ()}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 ()}
使用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 ()}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 ({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; returnnow:{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; returnnow:{count},before:{prevCount}
}