文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

学习React-Hook,我们应该思考哪些东西

2024-12-03 11:13

关注

组合优于继承,我们一直在寻求「 解耦 」来把复杂的业务代码简单化。而Hook的最大优点就是代码复用,更简洁。而且Hook在写法上也遵循了「 单一职责模式 」。

今天主要分享的是在学习Hook之前,我们应该做哪些思考。

数据绑定

在react中state的概念是内部状态的管理,它的变更直接会影响页面的渲染。

在hook中把setState拆开了。每一个单个的state的都会是一个单个的状态,单个状态的变更不会影响其他state。我们可以通过useState实现单个状态的初始化定义。

useState的参数可以是一个数字、字符串、布尔值、数组或对象,也可以是一个函数。

同样我们避免不了会使用集合(对象)来处理一些逻辑。

  1. const [count, setCount] = useState(0); 
  2. const [count, setCount] = useState(() => 0); 
  3. const [obj, setObj] = useState({}); 
  4. setObj((prevObj) => { 
  5.   // 也可以使用 Object.assign 
  6.   return { ...prevObj, age: 23 }; 
  7. }); 

一般我们会定义一个初始值initialState,如果这个初始值,需要额外的计算开销,我们可以定义一个函数来处理。需要注意的是,useState 函数只会在初始渲染的时候被调用。

  1. const [state, setState] = useState(() => { 
  2.   // 额外的操作someExpensiveComputation 
  3.   const initialState = someExpensiveComputation(props); 
  4.   return initialState; 
  5. }); 

对于多个state集合的处理,还有另一种方案就是useReducer。

如下多个 state 会随着登录、登出、刷新 token 这三种状态的改变而改变。

  1. const [state, dispatch] = React.useReducer( 
  2.   (prevState, action) => { 
  3.     switch (action.type) { 
  4.       case "RESTORE_TOKEN"
  5.         return { 
  6.           ...prevState, 
  7.           userToken: action.token, 
  8.           isLoading: false
  9.         }; 
  10.       case "SIGN_IN"
  11.         return { 
  12.           ...prevState, 
  13.           isSignout: false
  14.           userToken: action.token, 
  15.         }; 
  16.       case "SIGN_OUT"
  17.         return { 
  18.           ...prevState, 
  19.           isSignout: true
  20.           userToken: null
  21.         }; 
  22.     } 
  23.   }, 
  24.   { 
  25.     isLoading: true
  26.     isSignout: false
  27.     userToken: null
  28.   } 
  29. ); 

副作用

hook 提供了一种新的概念来代替生命周期函数,就是useEffect副作用。它被看作是从 React 的纯函数式世界通往命令式世界的逃生通道。

它的执行时机是在屏幕元素渲染结束后延迟执行。

它的作用有:

  1. // 通过的第二个参数来实现只执行一次或监控state值 
  2. useEffect(() => { 
  3.   // ... 
  4. }, []); 
  5.  
  6. // useEffect第一个参数的返回函数就是componentWillUnmount的思想,在组件卸载之前进行 
  7. useEffect(() => { 
  8.   const subscription = props.source.subscribe(); 
  9.   return () => { 
  10.     // 清除订阅 
  11.     subscription.unsubscribe(); 
  12.   }; 
  13. }); 

但是这种延迟执行的机制不能满足我们所有的场景,如果我们想要实现屏幕绘制和副作用同步执行,比如实时修改dom结构等这样的场景,useEffect无法满足,会出现闪屏的效果。

我们可以通过useLayoutEffect来实现,它的执行时机是在组件加载完成后,屏幕绘制之前进行。但这样也有缺点就是阻塞屏幕渲染,可能会出现白屏或停顿。

所以useLayoutEffect的使用场景:

如果只是单独的获取(get操作)就没有必要使用useLayoutEffect。

组件传值

组件传值的核心:

  1. // 父组件 
  2. function Home() { 
  3.   const [currentTab, setCurrentTab] = useState("msg"); 
  4.   return ( 
  5.     <> 
  6.       <View style={styles.logo}> 
  7.          // 父传子 
  8.          
  9.         // 子传父 
  10.         setCurrentTab(code)} /> 
  11.       View
  12.       {currentTab} 
  13.      
  14.   ); 
  15.  
  16. //子组件 
  17. function TabView({ currentTab, setCurrentTab }) { 
  18.   return ( 
  19.     <View style={styles.logo}> 
  20.       {currentTab} 
  21.       
  22.         title="修改tab" 
  23.         onPress={() => { 
  24.           setCurrentTab("pass"); 
  25.         }} 
  26.       /> 
  27.     View
  28.   ); 
  29. //子传父 
  30. function CodeView({ code, changeCode }) { 
  31.   return ( 
  32.     <View style={styles.logo}> 
  33.       {code} 
  34.       
  35.         title="修改tab" 
  36.         onPress={changeCode} 
  37.       /> 
  38.     View
  39.   ); 

多组件的传值,可以通过context来处理。 

  1. export const MyContent = React.createContext({}); 
  2.  
  3. function Home() { 
  4.   return ( 
  5.     
  6.       value={{ 
  7.         currentTab, 
  8.         phoneValue, 
  9.         codeValue, 
  10.         setPhoneValue, 
  11.         setCodeValue, 
  12.       }} 
  13.     > 
  14.        
  15.        
  16.      
  17.   ); 
  18.  
  19. function FormItem() { 
  20.   const { phoneValue, setPhoneValue } = useContext(MyContent); 
  21.   return ( 
  22.     <View style={styles.logo}> 
  23.       {phoneValue} 
  24.       {} 
  25.     View
  26.   ); 
  27.  
  28. function SwitchItemView() { 
  29.   const { codeValue, setCodeValue } = useContext(MyContent); 
  30.   return ( 
  31.     <View style={styles.logo}> 
  32.       {phoneValue} 
  33.       {} 
  34.     View
  35.   ); 

元素节点操作

hook通过useRef来创建节点对象,然后通过ref挂载,通过current来获取。

  1. function TextInputWithFocusButton() { 
  2.   const inputEl = useRef(null); 
  3.   const onButtonClick = () => { 
  4.     inputEl.current.focus(); 
  5.   }; 
  6.   return ( 
  7.     <> 
  8.       "text" /> 
  9.       Focus the input 
  10.      
  11.   ); 

我们可能会封装一些逻辑,自定义一些属性,暴露给父元素,那么我们就会用到useImperativeHandle和forwardRef。

  1. function FancyInput(props, pref) { 
  2.   const inputRef = useRef(); 
  3.   useImperativeHandle(ref, () => ({ 
  4.     focus: () => { 
  5.       inputRef.current.focus(); 
  6.     } 
  7.   })); 
  8.   return 
  9.  
  10. FancyInput = forwardRef(FancyInput); 
  11.  
  12.   
  13. // 父组件可以直接调用inputRef.current.focus() 

因为 ref 对象不会把当前 ref 值的变化通知给我们,所以我们必须通过useState和useCallback实现。

  1. function MeasureExample() { 
  2.   const [height, setHeight] = useState(0); 
  3.  
  4.   const measuredRef = useCallback((node) => { 
  5.     if (node !== null) { 
  6.       setHeight(node.getBoundingClientRect().height); 
  7.     } 
  8.   }, []); 
  9.  
  10.   return ( 
  11.     <> 
  12.        
  13.       

    The above header is {Math.round(height)}px tall

     
  14.      
  15.   ); 

自定义hook

在代码中,我们会有一些共用的逻辑,我们可以抽离出来比如自定义的防抖节流,自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

  1. const useDebounce = (fn, ms = 30, deps = []) => { 
  2.   let timeout = useRef(); 
  3.  
  4.   useEffect(() => { 
  5.     if (timeout.current) clearTimeout(timeout.current); 
  6.     timeout.current = setTimeout(() => { 
  7.       fn(); 
  8.     }, ms); 
  9.   }, deps); 
  10.  
  11.   const cancel = () => { 
  12.     clearTimeout(timeout.current); 
  13.     timeout = null
  14.   }; 
  15.   return [cancel]; 
  16. }; 
  17.  
  18. export default useDebounce; 
  19. const Home = (props) => { 
  20.   const [a, setA] = useState(0); 
  21.   const [b, setB] = useState(0); 
  22.   const [cancel] = useDebounce( 
  23.     () => { 
  24.       setB(a); 
  25.     }, 
  26.     2000, 
  27.     [a] 
  28.   ); 
  29.  
  30.   const changeIpt = (e) => { 
  31.     setA(e.target.value); 
  32.   }; 
  33.  
  34.   return ( 
  35.     
     
  36.       "text" onChange={changeIpt} /> 
  37.       {b} {a} 
  38.     
 
  •   ); 
  • }; 
  • 性能优化

    单向数据流,各组件层次分明,状态明确,但是当项目体量大,组件嵌套多的时候,性能损耗也非常大,所以我们会做一些性能优化的工作。

    可能的优化场景有:

    其实useMemo和useCallback的核心思想相同,都是记录上一次的输入,如果下一次输入与上一次相同,将不会计算,直接获取上一次的结果。他们的区别只是形式上的,useMemo返回一个 memoized 值。而useCallback返回的是memoized回调函数。

    useMemo缓存计算结果的值。

    useCallback主要用于缓存函数。

    useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

    1. const memoizedCallback = useCallback(() => { 
    2.   doSomething(a, b); 
    3. }, [a, b]); 
    4.  
    5. // useMemo 
    6. const [count, setCount] = useState(1); 
    7. const [val, setValue] = useState(""); 
    8. const expensive = useMemo(() => { 
    9.   let sum = 0; 
    10.   for (let i = 0; i < count * 100; i++) { 
    11.     sum += i; 
    12.   } 
    13.   return sum
    14. }, [count]); 
    15.  
    16. return ( 
    17.   
       
    18.     

       

    19.       {count}-{expensive} 
    20.      
    21.     {val} 
    22.     
       
    23.        setCount(count + 1)}>+c1 
    24.        setValue(event.target.value)} /> 
    25.     
     
  •    
  • ); 
  • 捎带了解一下memoized,简单讲就是把函数的计算结果缓存起来,比如递归。

    1. const memoize = function(fn) { 
    2.     const cache = {}; 
    3.     return function() { 
    4.         const key = JSON.stringify(arguments); 
    5.         var value = cache[key]; 
    6.         if(!value) { 
    7.             console.log('新值,执行中...');         // 为了了解过程加入的log,正式场合应该去掉 
    8.             value = [fn.apply(this, arguments)];  // 放在一个数组中,方便应对undefined,null等异常情况 
    9.             cache[key] = value; 
    10.         } else { 
    11.             console.log('来自缓存');               // 为了了解过程加入的log,正式场合应该去掉 
    12.         } 
    13.         return value[0]; 
    14.     } 
    15.  
    16. module.exports = memoize; 
    17. const memoize = require('./memoize.js'); 
    18. const log = console.log; 
    19.  
    20. // 斐波那契数组 
    21. const fibonacci = (n) => { 
    22.     return n < 2  
    23.         ? n 
    24.         : fibonacci(n - 1) + fibonacci(n - 2); 
    25. }; 
    26.  
    27. const memoizeFibonacci = memoize(fibonacci); 
    28.  
    29. log(memoizeFibonacci(45));   // 新值,执行中...;    1134903170  // 等待时间比较长 
    30. log(memoizeFibonacci(45));   // 来自缓存;    1134903170 
    31. log(memoizeFibonacci(45));   // 来自缓存;    1134903170 
    32. log(memoizeFibonacci(45));   // 来自缓存;    1134903170 

    本文转载自微信公众号「 惊天码盗」,可以通过以下二维码关注。转载本文请联系 惊天码盗公众号。

    来源:惊天码盗内容投诉

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    软考中级精品资料免费领

    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容

    猜你喜欢

    AI推送时光机
    位置:首页-资讯-后端开发
    咦!没有更多了?去看看其它编程学习网 内容吧
    首页课程
    资料下载
    问答资讯