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

 

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

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

数据绑定、东西状态管理 生命周期、学习组件挂载卸载 组件传值 节点元素获取 复用逻辑抽离

数据绑定

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

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

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

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

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

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

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

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

比如单个 state 的状态会影响多个 state 的值的时候。 比如多个 state 的状态会随着某种类型的改变而改变。

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

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,   } ); 

副作用

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

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

它的作用有:

它可以监控 state 值的变化 它可以处理只运行一次的逻辑,有点类似生命周期 componentDidMount 和 componentWillUnmount 的思维模式, 添加订阅、设置定时器、发送网络请求 更多其他 // 通过的第二个参数来实现只执行一次或监控state值 useEffect(() => {    // ... }, []); // useEffect第一个参数的返回函数就是componentWillUnmount的思想,在组件卸载之前进行 useEffect(() => {    const subscription = props.source.subscribe();   return () => {      // 清除订阅     subscription.unsubscribe();   }; }); 

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

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

所以useLayoutEffect的使用场景:

防止闪烁,比较耗时的计算 Dom操作 componentDidMount和componentDidUpdate的场景

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

组件传值

组件传值的核心:

父传子,通过在子组件设置属性; 子传父,通过回调。 多级组件,通过中间状态管理 // 父组件 function Home() {    const [currentTab, setCurrentTab] = useState("msg");   return (     <>       <View style={ styles.logo}>          // 父传子         <TabView currentTab={ currentTab} setCurrentTab={ setCurrentTab} />         // 子传父         <CodeView code={ code} changeCode={ (code)=>setCurrentTab(code)} />       </View>       <Text>{ currentTab}</Text>     </>   ); } //子组件 function TabView({  currentTab, setCurrentTab }) {    return (     <View style={ styles.logo}>       <Text>{ currentTab}</Text>       <Button         title="修改tab"         onPress={ () => {            setCurrentTab("pass");         }}       />     </View>   ); } //子传父 function CodeView({  code, changeCode }) {    return (     <View style={ styles.logo}>       <Text>{ code}</Text>       <Button         title="修改tab"         onPress={ changeCode}       />     </View>   ); } 

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

export const MyContent = React.createContext({ }); function Home() {    return (     <MyContent.Provider       value={ {          currentTab,         phoneValue,         codeValue,         setPhoneValue,         setCodeValue,       }}     >       <FormItem />       <SwitchItemView />     </MyContent.Provider>   ); } function FormItem() {    const {  phoneValue, setPhoneValue } = useContext(MyContent);   return (     <View style={ styles.logo}>       <Text>{ phoneValue}</Text>       { /* ...*/}     </View>   ); } function SwitchItemView() {    const {  codeValue, setCodeValue } = useContext(MyContent);   return (     <View style={ styles.logo}>       <Text>{ phoneValue}</Text>       { /* ...*/}     </View>   ); } 

元素节点操作

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

function TextInputWithFocusButton() {    const inputEl = useRef(null);   const onButtonClick = () => {      inputEl.current.focus();   };   return (     <>       <input ref={ inputEl} type="text" />       <button onClick={ onButtonClick}>Focus the input</button>     </>   ); } 

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

function FancyInput(props, pref) {    const inputRef = useRef();   useImperativeHandle(ref, () => ({      focus: () => {        inputRef.current.focus();     }   }));   return <input ref={ inputRef} ... />; } FancyInput = forwardRef(FancyInput); <FancyInput ref={ inputRef} />  // 父组件可以直接调用inputRef.current.focus() 

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

function MeasureExample() {    const [height, setHeight] = useState(0);   const measuredRef = useCallback((node) => {      if (node !== null) {        setHeight(node.getBoundingClientRect().height);     }   }, []);   return (     <>       <h1 ref={ measuredRef}>Hello, world</h1>       <h2>The above header is { Math.round(height)}px tall</h2>     </>   ); } 

自定义hook

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

const useDebounce = (fn, ms = 30, deps = []) => {    let timeout = useRef();   useEffect(() => {      if (timeout.current) clearTimeout(timeout.current);     timeout.current = setTimeout(() => {        fn();     }, ms);   }, deps);   const cancel = () => {      clearTimeout(timeout.current);     timeout = null;   };   return [cancel]; }; export default useDebounce; const Home = (props) => {    const [a, setA] = useState(0);   const [b, setB] = useState(0);   const [cancel] = useDebounce(     () => {        setB(a);     },     2000,     [a]   );   const changeIpt = (e) => {      setA(e.target.value);   };   return (     <div>       <input type="text" onChange={ changeIpt} />       { b} { a}     </div>   ); }; 

性能优化

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

可能的优化场景有:

单个 state 的更新会影响全局,有一点需要注意的是,被context包裹的组件,只要value的任何一个变动,都会重新渲染,useMemo和useCallback就会失效。 相同的输入,不再重新计算 父组件的函数在子组件使用的时候

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

useMemo缓存计算结果的值。

useCallback主要用于缓存函数。

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

const memoizedCallback = useCallback(() => {    doSomething(a, b); }, [a, b]); // useMemo const [count, setCount] = useState(1); const [val, setValue] = useState(""); const expensive = useMemo(() => {    let sum = 0;   for (let i = 0; i < count * 100; i++) {      sum += i;   }   return sum; }, [count]); return (   <div>     <h4>       { count}-{ expensive}     </h4>     { val}     <div>       <button onClick={ () => setCount(count + 1)}>+c1</button>       <input value={ val} onChange={ (event) => setValue(event.target.value)} />     </div>   </div> ); 

捎带了解一下memoized,简单讲就是把函数的计算结果缓存起来,比如递归。

const memoize = function(fn) {      const cache = { };     return function() {          const key = JSON.stringify(arguments);         var value = cache[key];         if(!value) {              console.log(新值,执行中...);         // 为了了解过程加入的log,正式场合应该去掉             value = [fn.apply(this, arguments)];  // 放在一个数组中,方便应对undefined,null等异常情况             cache[key] = value;         } else {              console.log(来自缓存);               // 为了了解过程加入的log,正式场合应该去掉         }         return value[0];     } } module.exports = memoize; const memoize = require(./memoize.js); const log = console.log; // 斐波那契数组 const fibonacci = (n) => {      return n < 2          ? n         : fibonacci(n - 1) + fibonacci(n - 2); }; const memoizeFibonacci = memoize(fibonacci); log(memoizeFibonacci(45));   // 新值,执行中...;    1134903170  // 等待时间比较长 log(memoizeFibonacci(45));   // 来自缓存;    1134903170 log(memoizeFibonacci(45));   // 来自缓存;    1134903170 log(memoizeFibonacci(45));   // 来自缓存;    1134903170 

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

滇ICP备2023000592号-31