-
高阶组件HOC的用法与封装
- 复用组件逻辑的一种高级技巧、设计模式
- 接受组件为参数返回新组件(为纯函数,不改变传入的组件的前提下返回新组件)
ts//登录高阶组件实现 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> }
- 反向继承
- 通过继承被包装组件来实现功能增强()
ts// 原始组件 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" />; }
-
状态抽离
- 指的是将组件的状态与逻辑从组件中抽离出来,使得组件可复用的一种方式
ts//状态切换高阶组件 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并传递给被包装组件
tsinterface 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) => { //新增props const 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库来解决
tsimport { useImmer } from 'use-immer'; const [user, updateUser] = useImmer({ name: '张三', age: 25, address: { city: '北京', district: '朝阳区' }, hobbies: ['阅读', '音乐'] });
-
函数式更新
tssetCount(prevCount => prevCount + 1);
-
惰性初始化(只在初始渲染的时候才执行一次)
tsconst computeExpensiveValue = () => { //这里是庞大的计算 } const [data, setData] = useState(() => { const expensiveValue = computeExpensiveValue(); return expensiveValue; });
-
-
useEffect
-
有依赖项 按依赖项的变动来触发 componetDidUpdate,如果没有依赖性相当于触发 componentDidMount;
-
异步函数的使用 (避免直接使用异步函数例如:
useEffect(async () => {}, [])
)javascriptuseEffect(() => { async function fetch() { let res = await api() } fetch() },[])
-
时间监听、定时器 以及 清理
javascriptuseEffect(() => { 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
ts//聊天室滚动条定位 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引用
tsconst App = () => { const inputRef = useRef<HTMLInputElement>(null) const handleBlur = () => { console.log(inputRef?.current?.value) } return ( <input ref={inputRef} onBlur={handleBlur} /> ); };
-
存储定时器或事件监听器
tsconst timer = useRef<number>(undefined); useEffect(() => { timer.current = setInterval(() => { console.log('定时器',timer) },1000) return () => clearInterval(timer?.current) });
-
存储可变值,不触发渲染
tsconst 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初始值只在挂载时设置一次
-
-
useMemo
- 缓存的是计算后的值(根据依赖渲染期间计算,计算的值参与渲染)
- 高开销计算、避免重复渲染
- 避免重新创建引用
- 依赖项目笔画频率低
javascriptinterface 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> ) }
- 创建稳定的引用
tsconst obj = { theme: 'dark',size: 'large'} //这种方式重新渲染的时候会重新创建 const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []); // 空依赖数组,只创建一次
-
useCallback
- 缓存函数引用;避免重复创建函数 避免子组件不必要的渲染;
- 完整的依赖列表 避免闭包问题;
- 空依赖项 纯工具函数;
tsconst 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 中调用
ts//开关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> ); };