高级特性
1,protals(传送门):将子组件渲染到父组件之外。
实例场景:父组件的儿子是<Modal>组件,使用fixed定位虽然样式看着是在父组件之外了,但是打开控制台查看元素,Modal相关的html还是嵌套在id='App'里面。由于fixed定位的元素最好是直接放到body里具有更好的兼容性,所以需要使用protals让modal渲染到body下:
import ReactDom from 'react-dom';
class Test extends from React.component{
render(){
return ReactDom.createPortal(<div className="modal">这是一个模态框</div>, document.body)
}
}
常用场景:
fixed浏览器兼容性,父组件z-index太小,父组件设置了overflow:hidden
2,context:多层级组件传值
场景:使用props层层传递太啰嗦,使用redux太繁重。
那么可以选择使用context或者recoil
// A.jsx
const ThemeContext = React.creatContext('light')
class A extends React.Component{
this.state ={ theme: ''}
render(){
<ThemeContext.Provider value={this.state.theme} >
<B />
<button onClick={()=>setState({val:'hh'})} ></button>
</ThemeContext.Provider>
}
}
// B.jsx
class B extends React.Component{
render(){
<div>
<C />
</div>
}
}
// C.jsx(class组件)
class C extends React.Component{
const { theme }= this.context;
static contextType = ThemeContext
render(){
<div>
{theme}
</div>
}
}
C.contextType=ThemeContext // 如果不这样写,需要在C组件中将注释打开
// C.jsx (函数组件)
function C (){
return <ThemeContext.Consumer> {theme=>theme} </ThemeContext.Consumer>
}
3,高阶组件
多个组件共享一个方法(工厂模式)。一个函数:将一个组件包装好再返回,并赋予给它新的属性或方法,类似装饰器。
使用场景:同一个页面的两个组件都需要用到鼠标滚动获取坐标的方法,又不想在每个组件里写两套相同的方法。
// HOC.tsx
function HOC(component){
const HOCcomponent = ()=>{
const [res,setRes]=useState();
const handleMove = (e)=>{
setRes({x:e.clientX,y:e.clientY};
}
return <div onMouseMove={handleMove}>
<component mouse={res} {...props} />
</div>
}
return HOCcomponent
}
4,react合成事件
onClick = (e)=>e.target.scrollTop,类似click等dom的原生事件,在react中被重写了一遍。
合成事件 = 事件注册、事件的合成、事件冒泡、事件派发
为什么要重写一遍:
抹平不同浏览器API的差异,方便跨平台
利用事件委托机制,简化dom事件处理逻辑,减小内存开销
react17之前和之后的区别:
react17之前事件是委托到document的,react18事件是委托到root上的
为什么18要更改委托点:
以下的代码,react17不会展示<div>看见我了</div>,react18会展示。
const [visible,setVisible] = useState(false);
const handleClick=(e)=>{
e.stopPropagation(); // react17无效
setVisible(true);
}
useEffect(()=>{
document.addEventListener('click',()=>{
setVisible(false)
})
<div id='root'>
<button onClick={handleClick}>点击</button>
</div>
{
visible?<div>看见我了</div>:null
}
原因:react虽然是合成事件但是也是基于事件委托的,所以会有事件冒泡。先执行了handleClick,react准备阻止往上冒泡,却发现,本次的click事件已经绑定到document上了,阻不阻止都是再同一个层级了,所以这个时候document还是能监听到click事件,就是stop失败。
于是react18给合成事件的事件委托到root根元素上,冒泡到root的时候stop,就不会冒泡到document了。
<html>. // docuemnt
<div id="roor"><./div>. // 根元素
</html>
5,batchUpdate机制
顺序执行setState({a: 123}),setState({b: 234})会被合并为一次更改state,但是setState({a: 123}),setState({a: 345})不会被合并为一次,而是两次。batchUpdate就是为了减少不必要的state刷新
6,react事务机制
7,react fiber
性能优化
1,异步加载组件:
组件比较大import()或者路由懒加载React.Lazy,React.Suspense
import():正常的组件会随着整个项目打包成一个MD5.js的文件,但是异步加载的组件会单独打包成一个md5.js文件
React.Lazy():相当于一个构造函数,将引入的组件作为输入,封装后输出一个异步的组件
React.Suspense
const Dom = React.lazy(()=>import('../bigComponent')
class App extends from React.component{
render(){
return <React.Suspense fullback={<div>...loading</div>}>
<Dom />
</React.Suspense>
}
}
2,scu
针对class组件使用shouldComponentUpdate,如果只是进行浅层比较可以使用PureComponent。
针对函数组件可以使用memo(配合useMemo,useCallback使用),如果浅层比较,memo函数第二个参数可以省略,如果深层比较第二个参数可以传一个类似shouldComponentUpdate那样的函数。
比如以下结构代码:
// index.tsx
const Index=()=>{
const [val,setVal]=useState(1);
const [b,setb]=useState('hh');
const add = ()=>setVal(val+1);
const callbackFn = ()=>alert('hi');
return <div>
<button onClick={add}>按钮{val}</button>
<B b={b} callbackFn={handle} />
</div>
}
// B.tsx
const B = (b,callbackFn)=>{
console.log('我又被执行了一遍,这就是刷新了这个组件')
return <div>
{b}
</div>
}
export default(B);
每次点击按钮B组件都会打印那句话,这就说明B组件的代码又被执行了一遍进行重复渲染。为了解决这个重复渲染,变量用useMemo封装起来,函数用useCallback封装起来
// index.tsx
const Index=()=>{
const [val,setVal]=useState(1);
const [b,setb]=useState('hh');
const add = ()=>setVal(val+1);
const bprops = useMemo(()=>b,[b]);
const callbackFn = useCallback(()=>alert('hi'),[]);
return <div>
<button onClick={add}>按钮{val}</button>
<B b={b} callbackFn={handle} />
</div>
}
// B.tsx
const B = (b,callbackFn)=>{
console.log('我又被执行了一遍,这就是刷新了这个组件')
return <div>
{b}
</div>
}
export default(B);
// 如果想要深层比较可以这样写
export default(B,isEqual);
function isEqual(preProps,nextProps){
if(preProps.b===nextProps.b)return false;
return true;
}
hooks的优势
1,class组件太大很难拆分
2,同一个方法需要分散到不同的生命周期中(事件监听的绑定和解绑)
3,复用逻辑复杂,HOC,Render props
注意点:
1,react hooks必须在最外层执行逻辑,因为它是严格依赖执行顺序的,如果在函数中执行碰到了类似if函数等其他语句,react向存储中拿值可能拿到了别的state的值。
react生态
分类:初始化工具 ,路由,样式,基础组件,功能组件,状态管理,构建工具,代码检查工具
初始化工具:create-react-app、react-app-rewired提供拓展能力,国内umi和dva,如果是开发组件库推荐create-react-library,如果是大规模组件开发使用storybook
路由:react-router
样式:style-components,emotion
基础组件:antd
功能组件:react-dnd、react-draggable拖拽,video-react播放视频,react-pdf-viewer查看pdf,react-window和react-virtualized用于长列表问题
状态管理:recoil,react-easy-state
构建工具:webpack,vite,rollup,esbuild
代码检查工具:代码规范检查eslint,编写代码测试jest,enzyme,react-testing-library,react-hooks-testing-library