React 进阶

  • 高阶组件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并传递给被包装组件
    ts 复制代码
    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) => {
        //新增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库来解决
      ts 复制代码
      import { useImmer } from 'use-immer';
      const [user, updateUser] = useImmer({
          name: '张三',
          age: 25,
          address: {
            city: '北京',
            district: '朝阳区'
          },
          hobbies: ['阅读', '音乐']
        });
    • 函数式更新

      ts 复制代码
           setCount(prevCount => prevCount + 1);
    • 惰性初始化(只在初始渲染的时候才执行一次)

      ts 复制代码
         const computeExpensiveValue = () => {
            //这里是庞大的计算
         }
         const [data, setData] = useState(() => {
           const expensiveValue = computeExpensiveValue();
           return expensiveValue;
         });
  • useEffect

    • 有依赖项 按依赖项的变动来触发 componetDidUpdate,如果没有依赖性相当于触发 componentDidMount;

    • 异步函数的使用 (避免直接使用异步函数例如:useEffect(async () => {}, [])

      javascript 复制代码
         useEffect(() => {
          async function fetch() {
            let res = await api()
          }
          fetch()
        },[])
    • 时间监听、定时器 以及 清理

      javascript 复制代码
        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
        1. 执行时机不同: 执行useLayoutEffect清除函数 => 执行 useLayoutEffect副作用 => 浏览器绘制 => 执行useEffect清除函数 => 执行 useEffect副作用
        2. 上述流程可以看到useLayoutEffect执行在浏览器渲染之前所以会阻塞浏览器的绘制
      • 使用场景:读取dom布局、尺寸、位置;避免闪烁; 立即同步更新ui
    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引用

      ts 复制代码
      const App = () => {
        const inputRef = useRef<HTMLInputElement>(null)
        const handleBlur = () => {
          console.log(inputRef?.current?.value)
        }
        return (
          <input ref={inputRef} onBlur={handleBlur}  />
        );
      };
    • 存储定时器或事件监听器

      ts 复制代码
       	const timer = useRef<number>(undefined);
       	  useEffect(() => {
       	    timer.current = setInterval(() => {
       	      console.log('定时器',timer)
       	    },1000)
       	    return () => clearInterval(timer?.current)
       	 });
    • 存储可变值,不触发渲染

      ts 复制代码
       	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初始值只在挂载时设置一次
  • useMemo

    • 缓存的是计算后的值(根据依赖渲染期间计算,计算的值参与渲染)
    • 高开销计算、避免重复渲染
    • 避免重新创建引用
    • 依赖项目笔画频率低
    javascript 复制代码
    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>
      )
    }
    • 创建稳定的引用
    ts 复制代码
    const obj = { theme: 'dark',size: 'large'} //这种方式重新渲染的时候会重新创建
    const config = useMemo(() => ({
     theme: 'dark',
     size: 'large'
    }), []); // 空依赖数组,只创建一次
  • useCallback

    • 缓存函数引用;避免重复创建函数 避免子组件不必要的渲染;
    • 完整的依赖列表 避免闭包问题;
    • 空依赖项 纯工具函数;
    ts 复制代码
      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 中调用
    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>
      );
    };
相关推荐
Olrookie2 小时前
ruoyi-vue(十四)——前端框架及package.json,vite.config.js, main.js文件介绍
前端·笔记
jeff渣渣富2 小时前
Taro 2.x 分包优化实践:如何防止分包文件被打包到主包
前端·javascript
谢尔登2 小时前
【React】React 哲学
前端·react.js·前端框架
wow_DG3 小时前
【Vue2 ✨】Vue2 入门之旅 · 进阶篇(八):Vuex 内部机制
前端·javascript·vue.js
若年封尘3 小时前
吃透 Vue 样式穿透:从 scoped 原理到组件库样式修改实战
前端·javascript·vue.js·样式穿透·scoped
掘金安东尼3 小时前
CSS 颜色混乱实验
前端·javascript·github
Zhen (Evan) Wang3 小时前
.NET 6 文件下载
java·前端·.net
前端码农.3 小时前
Element Plus 数字输入框箭头隐藏方案
前端·vue.js
李游Leo3 小时前
npm / yarn / pnpm 包管理器对比与最佳实践(含国内镜像源配置与缓存优化)
前端·缓存·npm