课程目标
了解高阶组件的用法,封装以及Hooks详解以及异步组件;
知识要点
高阶组件用法及封装
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
属性代理
使用组合的方式,将组件包装在容器上,依赖父子组件的生命周期关系来;
返回stateless的函数组件
返回class组件
操作props
// 可以通过属性代理,拦截父组件传递过来的porps并进行处理。 // 返回一个无状态的函数组件 function HOC(WrappedComponent) { const newProps = { type: 'HOC' }; return props =>; } // 返回一个有状态的 class 组件 function HOC(WrappedComponent) { return class extends React.Component { render() { const newProps = { type: 'HOC' }; return ; } }; } 抽象state
// 通过属性代理无法直接操作原组件的state,可以通过props和cb抽象state function HOC(WrappedComponent) { return class extends React.Component { constructor(props) { super(props); this.state = { name: '', }; this.onChange = this.onChange.bind(this); } onChange = (event) => { this.setState({ name: event.target.value, }) } render() { const newProps = { name: { value: this.state.name, onChange: this.onChange, }, }; return; } }; } // 使用 @HOC class Example extends Component { render() { return ; } }
反向继承
使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等;
可以通过super.render()获取传入组件的render,可以有选择的渲染劫持;
劫持原组件生命周期方法
function HOC(WrappedComponent){ const didMount = WrappedComponent.prototype.componentDidMount; // 继承了传入组件 return class HOC extends WrappedComponent { async componentDidMount(){ // 劫持 WrappedComponent 组件的生命周期 if (didMount) { await didMount.apply(this); } ... } render(){ //使用 super 调用传入组件的 render 方法 return super.render(); } } }读取/操作原组件的state属性代理和反向继承对比
function HOC(WrappedComponent){ const didMount = WrappedComponent.prototype.componentDidMount; // 继承了传入组件 return class HOC extends WrappedComponent { async componentDidMount(){ if (didMount) { await didMount.apply(this); } // 将 state 中的 number 值修改成 2 this.setState({ number: 2 }); } render(){ //使用 super 调用传入组件的 render 方法 return super.render(); } } }
属性代理和反向继承对比
属性代理:从“组合”角度出发,有利于从外部操作wrappedComp,可以操作props,或者在wrappedComp 外加一些拦截器(如条件渲染等);
反向继承:从“继承”角度出发,从内部操作wrappedComp,可以操作组件内部的state,生命周期和render等,功能能加强大;
Hooks详解
Hooks是react16.8以后新增的钩子API;
目的:增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。
为什么要使用Hooks?
开发友好,可扩展性强,抽离公共的方法或组件,Hook 使你在无需修改组件结构的情况下复用状态逻辑;
函数式编程,将组件中相互关联的部分根据业务逻辑拆分成更小的函数;
class更多作为语法糖,没有稳定的提案,且在开发过程中会出现不必要的优化点,Hooks无需学习复杂的函数式或响应式编程技术;
useState
const [number, setNumber] = useState(0);
setState支持stateless组件有自己的state;
入参:具体值或一个函数;
返回值:数组,第一项是state值,第二项负责派发数据更新,组件渲染;
useEffect
使用条件:当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;
不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate 与 componentwillreceiveprops;
useEffect 第一个参数为处理事件,第二个参数接收数组,为限定条件,当数组变化时触发事件,为[]只在组件初始化时触发;
useEffect第一个参数有返回时,一般用来消除副作用(如去除定时器、事件绑定等);
* 模拟数据交互 / function getUserInfo(a) return new Promise((resolve)=>{ setTimeout(()=>{ resolve({ name:a, age:16, }) },500) }) } const Demo = ({ a }) => { const [ userMessage , setUserMessage ] = useState({}) const [number, setNumber] = useState(0) const div= useRef() const handleResize =()=>{} useEffect(()=>{ getUserInfo(a).then(res=>{ setUserMessage(res) }) console.log(div.current) / div / window.addEventListener('resize', handleResize) / 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 , 如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount / },[ a ,number ]) return ({ userMessage.name } { userMessage.age }) } ———————————————————————————————————————————————— const Demo = ({ a }) => { const handleResize =()=>{} useEffect(()=>{ const timer = setInterval(()=>console.log(666),1000) window.addEventListener('resize', handleResize) / 此函数用于清除副作用 */ return function(){ clearInterval(timer) window.removeEventListener('resize', handleResize) } },[ a ]) return () }setNumber(1) } >{ number }
useLayoutEffect
渲染更新之前的 useEffect
useEffect: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 ;
useLayoutEffect : 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成;
const DemoUseLayoutEffect = () => {
const target = useRef()
useLayoutEffect(() => {
/*我们需要在dom绘制之前,移动dom到制定位置*/
const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
animate(target.current,{ x,y })
}, []);
return (
)
}
useRef
用来获取元素、缓存数据;
入参可以作为初始值
// 获取元素
const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* 表单组件 dom 节点 */
console.log(dom.current)
}
return
表单组件
}
// 缓存数据,小技巧
// 不同于useState,useRef改变值不会使comp re-render
const currenRef = useRef(InitialData)
currenRef.current = newValue
异步组件
React16.6中,引入了 React.lazy 和 React.Suspense 两个API,再配合动态 import() 语法就可以实现组件代码打包分割和异步加载。
传统模式:渲染组件-> 请求数据 -> 再渲染组件
异步模式:请求数据-> 渲染组件;
// demo
import React, { lazy, Suspense } from 'react';
// lazy 和 Suspense 配套使用,react原生支持代码分割
const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
class App extends React.Component {
render() {
return (
App
loading }>
);
}
}
export default App;
React V 16中引入,部分UI的JS错误不会导致整个应用崩溃;
错误边界是一种 React 组件,错误边界在 渲染期间、生命周期方法和整个组件树的构造函数 中捕获错误,且会渲染出备用UI而不是崩溃的组件。
// comp ErrorBoundary
import React from 'react'
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
console.log(error, errorInfo)
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return Something went wrong.
;
}
return this.props.children;
}
}
export default ErrorBoundary
// comp App
import React, from 'react';
import ErrorBoundary from './ErrorBoundary'
class App extends React.Component {
state = {
count: 1
}
render() {
const { count } = this.state
if (count === 3) {
throw new Error('I crashed!');
}
return (
App
{count}
)
}
}
export default App;
浙公网安备 33010602011771号