React 进阶
- 高阶组件HOC的用法与封装
- 复用组件逻辑的一种高级技巧、设计模式
- 接受组件为参数返回新组件(为纯函数,不改变传入的组件的前提下返回新组件)
//登录高阶组件实现 interface LoginProps {name: string } const Auth = (LoginCom:React.FC<LoginProps>):React.FC<LoginProps> =>{return (props) => {let isAuth = true; //模拟登录逻辑if(isAuth) {return <LoginCom {...props}></LoginCom>}else{return <div>您没有权限</div>}} } const LoginCom:React.FC<LoginProps> = (props:LoginProps) => {return <div>登录人{props.name}</div> } const AuthLoginCom = Auth(LoginCom) const App = () => {return <AuthLoginCom name='张三'></AuthLoginCom> }
- 反向继承
- 通过继承被包装组件来实现功能增强()
// 原始组件 class Message extends React.Component {render() {return <div>原始消息: {this.props.text}</div>;} } // 反向继承HOC function withEnhancement(WrappedComponent) {return class extends WrappedComponent {render() {const original = super.render();//通过super 劫持原始组件的 render return (<div style={{ border: '2px solid red', padding: '10px' }}><span>修改渲染</span>//修改渲染xxx{original}</div>);}}; } // 使用 const EnhancedMessage = withEnhancement(Message); function App() {return <EnhancedMessage text="Hello World" />; }
- 状态抽离
- 指的是将组件的状态与逻辑从组件中抽离出来,使得组件可复用的一种方式
//状态切换高阶组件 interface ToolProps {isOn: boolean,toolbtn: () => void } interface ToggleButtonProps {label: string; } const WhthTool = <P extends ToggleButtonProps>(WrappedComponent:React.FC<P & ToolProps>):React.FC<P> => {return (props: P) => {// 传入WrappedComponent组件的状态以及逻辑在这里维护const [isOn, setOn] = useState(false);const toolbtn = () => {setOn((pre) => !pre)}return <WrappedComponent {...props} isOn={isOn} toolbtn={toolbtn} />} } // 这里 ToolButton 组件的状态以及逻辑都抽离出去了 const ToolButton:React.FC<ToggleButtonProps & ToolProps> = ({isOn = false, label, toolbtn}) => {return <button onClick={toolbtn}>{label}:{isOn?'开':'关'}</button> } const EnhancedToggleButton = WhthTool (ToolButton) const App = () => {return (<div><EnhancedToggleButton label="电源" /><EnhancedToggleButton label="灯光" /></div>); }
- 属性代理
- 高阶组件接收一个组件,返回一个新组件,新组件负责管理props并传递给被包装组件
interface ToolProps {isOn: boolean, } interface ToolAllProps extends ToolProps {color: string,name: string } const ToolButton:React.FC<ToolAllProps> = ({isOn = false, name, color}) => {return <button>{color + name}:{isOn?'开':'关'}</button> } const whthTool = <P extends ToolProps>(WrappedComponent:React.ComponentType<ToolAllProps>):React.FC<P> => {return (props) => {//新增propsconst newProps = {...props,color: '红',name: '电源'}return <WrappedComponent {...newProps}/>} } const EnhancedToggleButton = whthTool(ToolButton) const App = () => {return (<div><EnhancedToggleButton isOn={true}/></div>); }
常用hooks
- useState
- 避免嵌套过深,因为useState 的更新逻辑是进行值得浅比较,使用的Object.is方法,对象嵌套过深值的变化 Object.is方法判断不出来
- 无法避免的时候可以使用 immer库来解决
import { useImmer } from 'use-immer'; const [user, updateUser] = useImmer({name: '张三',age: 25,address: {city: '北京',district: '朝阳区'},hobbies: ['阅读', '音乐']});
- 函数式更新
setCount(prevCount => prevCount + 1);
- 惰性初始化(只在初始渲染的时候才执行一次)
const computeExpensiveValue = () => {//这里是庞大的计算}const [data, setData] = useState(() => {const expensiveValue = computeExpensiveValue();return expensiveValue;});
- 避免嵌套过深,因为useState 的更新逻辑是进行值得浅比较,使用的Object.is方法,对象嵌套过深值的变化 Object.is方法判断不出来
- useEffect
- 有依赖项 按依赖项的变动来触发 componetDidUpdate,如果没有依赖性相当于触发 componentDidMount;
- 异步函数的使用 (避免直接使用异步函数例如:
useEffect(async () => {}, [])
)useEffect(() => {async function fetch() {let res = await api()}fetch()},[])
- 时间监听、定时器 以及 清理
useEffect(() => {const handleResize = () => {console.log("窗口大小改变:", window.innerWidth);};const timer = setInterval(() => {console.log("定时器执行");}, 1000);window.addEventListener("resize", handleResize);return () => {window.removeEventListener("resize", handleResize);clearInterval(timer)};}, []);
- useLayoutEffect
- useLayoutEffect vs useEffect
- 执行时机不同: 执行useLayoutEffect清除函数 => 执行 useLayoutEffect副作用 => 浏览器绘制 => 执行useEffect清除函数 => 执行 useEffect副作用
- 上述流程可以看到useLayoutEffect执行在浏览器渲染之前所以会阻塞浏览器的绘制
- 使用场景:读取dom布局、尺寸、位置;避免闪烁; 立即同步更新ui
- useLayoutEffect vs useEffect
//聊天室滚动条定位const [messages, setMessages] = useState([{ id: 1, text: '你好!', sender: 'other' },{ id: 2, text: '你好,最近怎么样?', sender: 'me' },]);//聊天元素const chatContainerRef = useRef(null);// 关键:使用 useLayoutEffect 来控制滚动useLayoutEffect(() => {const chatContainer = chatContainerRef.current;if (chatContainer) {// 在浏览器绘制前同步滚动到底部chatContainer.scrollTop = chatContainer.scrollHeight;}}, [messages]); // 当消息列表变化时触发
- useRef
- 访问dom引用
const App = () => {const inputRef = useRef<HTMLInputElement>(null)const handleBlur = () => {console.log(inputRef?.current?.value)}return (<input ref={inputRef} onBlur={handleBlur} />); };
- 存储定时器或事件监听器
const timer = useRef<number>(undefined);useEffect(() => {timer.current = setInterval(() => {console.log('定时器',timer)},1000)return () => clearInterval(timer?.current)});
- 存储可变值,不触发渲染
const App = () => {const counter = useRef(0);const renderCount = useRef(0);useEffect(() => {renderCount.current += 1;});const increment = () => {counter.current += 1;console.log('当前值:', counter.current); // 不会触发重渲染};return (<div><p>组件渲染次数: {renderCount.current}</p><p>计数器值: {counter.current}</p><button onClick={increment}>增加</button></div>);};
- useRef 对比 useState
- useState会的setter函数会触发重新渲染, useRef 不会
- useState 为异步批量更新, useRef 为同步更新
- useState 初始值每次渲染可能重新赋值,useRef初始值只在挂载时设置一次
- 访问dom引用
- useMemo
- 缓存的是计算后的值(根据依赖渲染期间计算,计算的值参与渲染)
- 高开销计算、避免重复渲染
- 避免重新创建引用
- 依赖项目笔画频率低
interface PersonProps {name: string,age: number } interface ChildProps{label: string } // 子组件 const Child:React.FC<ChildProps> = (props) => {console.log('触发子组件渲染') //这行只会在挂载阶段执行一次return <div>我是{props.label}</div> } const App = () => {const [info, setInfo] = useState<PersonProps>({name:'张三',age: 14})const [label, setLabel] = useState<string>('子组件')const handleClick = ():void => {setInfo({name: '王五', age:20})}//这里使用useMemo包裹Child 组件,当App 重新渲染的时候不会触发Child组件的重新渲染const newChild = useMemo(() => <Child label= {label}></Child>, [])return (<div><span>姓名:{info.name}, 年龄:{info.age}</span><button onClick={handleClick}>换人</button>{newChild}</div>) }
- 创建稳定的引用
const obj = { theme: 'dark',size: 'large'} //这种方式重新渲染的时候会重新创建 const config = useMemo(() => ({theme: 'dark',size: 'large' }), []); // 空依赖数组,只创建一次
- useCallback
- 缓存函数引用;避免重复创建函数 避免子组件不必要的渲染;
- 完整的依赖列表 避免闭包问题;
- 空依赖项 纯工具函数;
const Component = () => {const [count, setCount] = useState<number>(0);const [name, setName] = useState<string>("张三");const handleClick_F = useCallback((): void => {console.log("第一个按钮", count, name);}, [count, name]); // 依赖 count 和 name 重新创建引用const handleClick_S = useCallback((): void => { //重新渲染不会重新创建引用console.log("第二个按钮");}, []);return (<div><button onClick={handleClick_F}>第一个按钮</button><button onClick={handleClick_S}>第二个按钮</button></div>);};
- useReducer 、useContext 为状态管理相关,会单独出一期React的状态管理
- 自定义hooks
- 名称必须以 use 开头
- 可以调用其他 Hook
- 只能在 React 函数组件或自定义 Hook 中调用
//开关hooks const useToogle = (init:boolean) => {const [value, setValue] = useState<boolean>(init);const toogleBtn = useCallback(() => setValue(prev => !prev),[])return [value, toogleBtn] } const App = () => {const [isOn, toogleBtn] = useToogle(false)return (<div><button onClick={toogleBtn}>当前灯:{isOn?'开': '关'}</button></div>); };