当前位置: 首页 > 科技观察

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

时间:2023-03-17 12:07:33 科技观察

组合比继承更好。我们一直在寻求“解耦”来简化复杂的业务代码。Hook最大的好处就是代码复用,更加简洁。而且,Hook在编写上也遵循“单一职责模型”。我们今天主要分享的是在学习Hook之前我们应该思考的问题。数据绑定、状态管理、生命周期、组件挂载和卸载、组件值传递、节点元素获取、复用逻辑提取和数据绑定。React中状态的概念是对内部状态的管理,它的变化会直接影响页面的渲染。SetState在hook中被反汇编。每个单一状态将是单一状态,单一状态的变化不会影响其他状态。我们可以通过useState来实现单个状态的初始化定义。useState的参数可以是数字、字符串、布尔值、数组或对象,也可以是函数。同样,我们也不能避免使用集合(对象)来处理一些逻辑。const[count,setCount]=useState(0);const[count,setCount]=useState(()=>0);const[obj,setObj]=useState({});setObj((prevObj)=>{//你也可以使用Object.assignreturn{...prevObj,age:23};});一般我们会定义一个初始值initialState。如果这个初始值需要额外的计算开销,我们可以定义一个函数来处理它。需要注意的是,useState函数只会在初始渲染时被调用。const[state,setState]=useState(()=>{//附加操作someExpensiveComputationconstinitialState=someExpensiveComputation(props);returninitialState;});对于多状态集合的处理,另一种方案是使用Reducer。例如,当单个状态的状态影响多个状态的值时。例如,多个状态的状态会随着某种类型的变化而变化。下面的多个状态会随着登录、注销、刷新令牌三种状态的变化而变化。const[state,dispatch]=React.useReducer((prevState,action)=>{switch(action.type){case"RESTORE_TOKEN":return{...prevState,userToken:action.token,isLoading:false,};case"SIGN_IN":return{...prevState,isSignout:false,userToken:action.token,};case"SIGN_OUT":return{...prevState,isSignout:true,userToken:null,};}},{isLoading:true,isSignout:false,userToken:null,});sideeffecthook提供了一个新的概念来替代生命周期函数,这就是useEffectsideeffect。它被视为从React的纯函数世界到命令世界的逃生路线。它的执行时机是在屏幕元素渲染完成后延迟执行。其功能如下:可以监视状态值的变化。可以处理只运行一次的逻辑,有点类似于生命周期componentDidMount和componentWillUnmount的思维模式。添加订阅,设置定时器,发送网络请求等等两个参数只执行一次或者监听状态值useEffect(()=>{//...},[]);//第一个参数的返回函数useEffect的思路是componentWillUnmount,在组件卸载前执行useEffect(()=>{constsubscription=props.source.subscribe();return()=>{//清除订阅subscription.unsubscribe();};});但是这种延迟执行机制并不能满足我们所有的场景,如果我们要实现屏幕绘制和副作用同时执行,比如实时修改dom结构等,useEffect无法满足,会出现闪屏影响。我们可以通过useLayoutEffect来实现,它在组件加载之后,屏幕绘制之前执行。但是这样也有阻塞屏幕渲染的缺点,可能会导致白屏或者卡顿。所以useLayoutEffect的使用场景:防止闪烁,计算耗时如果componentDidMount和componentDidUpdate的场景只是单独获取(get操作),没必要使用useLayoutEffect。组件传值的核心:父子传值,通过在子组件中设置属性;通过回调从孩子传递给父母。多级组件,通过中间状态管理//父组件functionHome(){const[currentTab,setCurrentTab]=useState("msg");return(<>//Parenttochild//从子到父setCurrentTab(code)}/>{currentTab}/Text>);}//子组件函数TabView({currentTab,setCurrentTab}){return({currentTab}{setCurrentTab("pass");}}/>);}//父函数CodeView({code,changeCode}){return({code});}多个组件的传值可以通过context来处理。exportconstMyContent=React.createContext({});functionHome(){return();}functionFormItem(){const{phoneValue,setPhoneValue}=useContext(MyContent);return({phoneValue}{/*...*/>);}functionSwitchItemView(){const{codeValue,setCodeValue}=useContext(MyContent);return({phoneValue}{/*...*});}元素节点操作钩子通过useRef创建节点对象,然后通过ref挂载,通过current获取。functionTextInputWithFocusButton(){constinputEl=useRef(null);constonButtonClick=()=>{inputEl.current.focus();};return(<>Focustheinput/>);}我们可能会封装一些逻辑,自定义一些属性,暴露给父元素,然后我们会用到useImperativeHandle和forwardRef。functionFancyInput(props,pref){constinputRef=useRef();useImperativeHandle(ref,()=>({focus:()=>{inputRef.current.focus();}}));return;}FancyInput=forwardRef(FancyInput);//父组件可以直接调用inputRef.current.focus()因为ref对象不会通知我们currentrefvalue,所以我们必须通过useState和useCallback来实现。functionMeasureExample(){const[height,setHeight]=useState(0);constmeasuredRef=useCallback((node)=>{if(node!==null){setHeight(node.getBoundingClientRect().height);}},[]);return(<>Hello,world

Theaboveheaderis{Math.round(height)}pxtall

/>);}代码中自定义hook,我们会有一些通用的逻辑,我们可以提取出来,比如自定义防抖节流,自定义Hook是一个函数,它的名字以“use”开头,在函数内部可以调用其他的Hook。constuseDebounce=(fn,ms=30,deps=[])=>{lettimeout=useRef();useEffect(()=>{if(timeout.current)clearTimeout(timeout.current);timeout.current=setTimeout(()=>{fn();},ms);},deps);constcancel=()=>{clearTimeout(timeout.current);timeout=null;};return[cancel];};exportdefaultuseDebounce;constHome=(props)=>{const[a,setA]=useState(0);const[b,setB]=useState(0);const[cancel]=useDebounce(()=>{setB(a);},2000,[a]);constchangeIpt=(e)=>{setA(e.target.value);};return(
{b}{a}div>);};性能优化单向数据流,每个组件层级清晰,状态清晰,但是当项目规模较大,组件嵌套较多时,性能损失也很大,所以我们将做一些性能优化工作。可能的优化场景包括:单个状态更新将影响整个世界。需要注意的一点是,只要context包裹的组件的值发生任何变化,就会重新渲染,useMemo和useCallback就会失效。同样的输入不会重新计算父组件的功能。使用子组件时,useMemo和useCallback的核心思想是一样的。他们都记录最后一次输入。如果下一个输入与上一个输入相同,则不会计算。直接得到最后的结果。它们的区别只是形式上的,useMemo返回一个记忆值。useCallback返回记忆回调函数。useMemo缓存计算结果的值。useCallback主要用于缓存函数。useCallback(fn,deps)等同于useMemo(()=>fn,deps)。constmemoizedCallback=useCallback(()=>{doSomething(a,b);},[a,b]);//useMemoconst[count,setCount]=useState(1);const[val,setValue]=useState("");constexpensive=useMemo(()=>{letsum=0;for(leti=0;i

{count}-{expensive}

{val}setCount(count+1)}>+c1setValue(event.target.value)}/>
);顺便了解一下memoized。简单的说就是缓存函数的计算结果,比如递归。constmemoize=function(fn){constcache={};returnfunction(){constkey=JSON.stringify(arguments);varvalue=cache[key];if(!value){console.log('新值,正在执行..');//为了理解进程添加的日志,正式场合要去掉value=[fn.apply(this,arguments)];//放在数组中,方便undefined,null等异常情况cache[key]=value;}else{console.log('fromcache');//为了了解进程添加的log,}returnvalue[0];}}module.exports=memoize;constmemoize=require('./memoize.js');constlog=console.log;//斐波那契数列constfibonacci=(n)=>{return<2?n:fibonacci(n-1)+fibonacci(n-2);};constmemoizeFibonacci=memoize(fibonacci);log(memoizeFibonacci(45));//新值,正在执行...;1134903170//等待时间比较长log(memoizeFibonacci(45));//来自缓存;1134903170log(memoizeFibonacci(45));//来自缓存;1134903170log(memoizeFibonacci(45));//来自缓存;1134903170本文转载自微信公众号“惊爆代码小偷”,可通过以下关注二维码。转载本文请联系惊码盗公众号。