ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

React Hooks

2022-08-18 13:32:25  阅读:132  来源: 互联网

标签:const Hooks React useState 组件 return useEffect


Hooks

概念

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。Hook是 React 16.8 (当前版本18,项目使用17)的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

优势:

  • 函数组件不能使用state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks让函数组件更靠近class组件,拥抱函数式编程。
  • 解决副作⽤问题,hooks出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect
  • 更好写出有状态的逻辑重用组件。
  • 让复杂逻辑简单化,比如状态管理:useReduceruseContext
  • 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
  • 更容易发现无用的状态和函数。

基础Hook

useState

1、参数:useState() 方法里面唯一的参数就是初始 state

2、返回值:当前 state 以及更新 state 的函数[count,setCount]

3、使用方式:

(1)使用直接调用count即可

(2)更新值使用setCount(3)

import React, { useState } from 'react';

function Example() {
  	// 声明一个叫 "count" 的 state 变量
  	const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>You clicked {this.state.count} times</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
                Click me
            </button>
        </div>
    );
}

useEffect

Effect Hook 可以让你在函数组件中执行副作用操作,所谓副作用就是:数据获取,设置订阅以及手动更改 React 组件中的 DOM等等操作。

通过这个钩子可以告诉 React 组件需要在渲染后执行某些操作。React 会保存传递的函数(effect),并且在执行 DOM 更新之后调用它。

注意事项:

1、useEffect 会在每次渲染后都执行

2、调用useEffect需要在组件内部(如下:Example内)

3、无需清除effect的写法

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

4、需清除effect的写法(通过返回函数来解决)

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  // 这里的useEffect返回了一个函数cleanup
  // 这个函数会在effect清除是执行
  // 对于这种添加和删除订阅的业务就可以用这种方式
  // 当我们关闭或刷新浏览器是都会触发cleanup清除订阅的业务,有效防止内存泄漏风险
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

5、跳过 Effect 进行性能优化(减少不必要的渲染)

通过传入第二个参数解决,只有在第二个参数发生变化时才会重新渲染。

如果要使用此优化方式,需确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量(就是这个变量需要在外部声明并在effect中使用),否则你的代码会引用到先前渲染中的旧变量。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

useContext

实现组件通信的一种手段。

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext providercontext value 值。即使祖先使用 React.memoshouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

React.createContextreact使用context实现跨组件通信的基础(创建)

使用方式:

1、创建

const MyContext = React.createContext(themes.light);

2、传值(长辈组件)

function App() {
  return (
    <MyContext.Provider value={themes.dark}>
      <div>
      	<MyButton />
      </div>
    </MyContext.Provider>
  );
}

3、接收(孩子组件)

function MyButton() {
  // useContext 接收值
  const params = useContext(MyContext);
  return (
    <div>
      接收到的context:{params}
    </div>
  );
}

其他Hook

useReducer

  • useReduceruseState的代替方案,主要用来解决复杂结构的statestate处理逻辑比较复杂的情况

  • useReducer 是单个组件状态管理,组件通讯还需要 props

  • redux 是全局状态管理,多组件共享数据

  • useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer

  • reduxvuex类型

    事实上useReducer其实和数组的reducer函数更像, 都是对复杂和大量对象进行某种运算得到一个简单结果, 不同的是useReducer产生的结果是一个state而已.

    实例(登录):

    useState方式:

    可以看出这种方式会有很多的useState,难以维护。

    	function LoginPage() {
    	
            const [name, setName] = useState(''); // 用户名
            const [pwd, setPwd] = useState(''); // 密码
            const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
            const [error, setError] = useState(''); // 错误信息
            const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录
    
            const login = (event) => {
                event.preventDefault();
                setError('');
                setIsLoading(true);
                login({ name, pwd })
                    .then(() => {
                        setIsLoggedIn(true);
                        setIsLoading(false);
                    })
                    .catch((error) => {
                        // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
                        setError(error.message);
                        setName('');
                        setPwd('');
                        setIsLoading(false);
                    });
            }
            return ( 
                //  返回页面JSX Element
            )
        }
    

    useReducer改造后,登录信息都在initState中清晰好维护:

    const initState = {
        name: '',
        pwd: '',
        isLoading: false,
        error: '',
        isLoggedIn: false,
    }
    function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true,
                    error: '',
                }
            case 'success':
                return {
                    ...state,
                    isLoggedIn: true,
                    isLoading: false,
                }
            case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: '',
                    pwd: '',
                    isLoading: false,
                }
            default: 
                return state;
        }
    }
    function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) => {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() => {
                    dispatch({ type: 'success' });
                })
                .catch((error) => {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        return ( 
            //  返回页面JSX Element
        )
    }
    

useMemo

useMemo是用来减少没必要的重复渲染,从而提高执行效率

代码示例:在下面这个代码中,当num发生变化的时候会重新触发渲染,导致list重复渲染

import React, { useState } from "react";

export default function App() {
  const [len] = useState(10);
  const [num, setNum] = useState(0);
  const list = [];
  for (let i = 0; i < len; i++) {
    console.log(1234);
    list.push(<li>{i}</li>);
  }
  return (
    <div>
      <ul>{list}</ul>
      <input
        type="text"
        value={num}
        onChange={(e) => {
          setNum(e.target.value);
        }}
      />
    </div>
  );
}

使用useMemo优化后代码:在num发生变化后,list组件不会重新渲染,触发渲染的条件来自于useMemo的第二个参数,只有数组中的变量发生变化才会触发list的重新渲染

import React, { useState, useMemo } from "react";

export default function App() {
  const [len] = useState(10);
  const [num, setNum] = useState(0);
  const list = useMemo(() => {
    const list = [];
    for (let i = 0; i < len; i++) {
      console.log(1234);
      list.push(<li>{i}</li>);
    }
    return list;
  }, [len]);

  return (
    <div>
      <ul>{list}</ul>
      <input
        type="text"
        value={num}
        onChange={(e) => {
          setNum(e.target.value);
        }}
      />
    </div>
  );
}

useCallback

useCallback的作用是用来避免子组件不必要的reRender(避免不必要的重复渲染),不是用来解决组件中有过多内部函数导致的性能问题

使用实例:

import {useCallback, useState} from "react";
 
export const UseCallbackExample = () => {
    const [count, setCount] = useState(0);
    const logCount = useCallback(() => {
        console.log(count);
    }, [count]);
 
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                click me
            </button>
            <button onClick={logCount}>log count</button>
        </div>
    );
}

useMemo与useCallback

  1. useCallbackuseMemo主要用来做数据缓存,它们的更新依赖于第二个参数是否发生了变化。
  2. useMemouseCallback是在页面渲染前执行的hooks,因此这两个hooks中执行的内容应该是纯函数,不会影响页面元素的更新,页面也就不会陷入死循环。

不同:

  1. useMemo返回的是一个变量的值,useCallback返回的是一个函数。对应到function组件最后要return的html代码部分,useMemo就是作为一个值来使用的,而useCallback则是被绑定到onClick上,作为要执行的函数。这就是它俩的本质区别。(useCallback针对函数,useMemo针对值)

  2. useCallback(fn, deps) 等价于 useMemo(() => fn, deps)

useRef

作用:

  1. useRef 用来获取DOM元素对象

    当我们需要获取元素对象的时候, 首先引入useRef, 其次调用useRef()方法接收它的返回值,我们需要获取那个DOM元素就在那个DOM元素上进行绑定,通过ref属性将useRef的返回值绑定到元素身上,这样useRef的返回值,通过useRef返回一个对象,对象内部有个current属性,这个属性就对应着我们需要的元素对象;

    import React, {useRef} from "react";
    
    function Ref (){
        const box = useRef()
    
        return (
            <div>
                <div ref={box}>useRef</div>
                <button onClick={() => console.log(box)}>+1</button>
            </div>
        )
    }
    export default Ref;
    
    
  2. 保存数据

    为什么不用let一个变量来保存数据, 因为在使用定时器更新状态数据时, 数值的每次变化都会引起组件的更新,每次更新都重新let一个变量,所以在进行解绑操作的时候,你的let变量为null,它并没有保存定时器,所以以上场景需要使用useRef进行保存数据,useRef不会因为组件的更新而丢失数据,虽然组件进行了更新,但是通过useRef保存的数据是不会丢失的,这里通过useRef中的current来进行保存也是官方要求的写法,所以如果你想要保存的数据不会因为组件的更新而丢失,就可以使用useRef来保存数据

    // 代码实例——保存数据
    import React, {useRef, useEffect, useState} from "react";
    
    function Ref (){
        let timerId = useRef()
        const [count, setCount] = useState(0)
        useEffect(() => {
            timerId.current = setInterval(() => {
                setCount(count => count + 1)
            }, 1000)
        }, [])
        const stop = () => {
            console.log(timerId)
            clearInterval(timerId.current)
        }
        return (
            <div>
                <div>{count}</div>
                <button onClick={stop}>停止</button>
            </div>
        )
    }
    export default Ref;
    
  3. useRef获取组件的方法

    import React, { useRef } from 'react'
    import { Child } from './child'
    
    export default function App() {
    
        const onRef = useRef();
        const pOnclick = ()=>{
            onRef.current.onclickAction();
        }
        
        return (
            <div>
                <span onClick={pOnclick}>点我调用子组件方法</span>
                <Child onRef={onRef} />
            </div>
        )
    }
    

useImperativeHandle

正常情况下 ref 是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle 为我们提供了一个类似实例的东西。在函数组件中就可以通过useImperativeHandle 将子组件的方法暴露出去供其他使用。

结合useRef第三点可以实现父组件调用子组件方法

import React, { useImperativeHandle } from 'react'

export const Child = ({ onRef }) => {

    const onclickAction = () => {
        console.log("我是子组件 我被调用了!!!")
    }

    useImperativeHandle(onRef, () => ({
        onclickAction,
    }))

    return (
        <span onClick={onclickAction} />
    )
}

useLayoutEffect

用法同useEffect

区别如下:

  • useEffect 是异步执行的,而useLayoutEffect是同步执行的。
  • useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。

总结:

  1. 优先使用 useEffect,因为它是异步执行的,不会阻塞渲染
  2. 会影响到渲染的操作尽量放到 useLayoutEffect中去,避免出现闪烁问题
  3. useLayoutEffectcomponentDidMount是等价的,会同步调用,阻塞渲染
  4. useLayoutEffect在服务端渲染的时候使用会有一个 warning,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致(解决这个问题需要自定义hook判断环境决定使用useEffect还是useLayoutEffect)。

样例:

import * as React from 'react';

function Test() {
  const [state, setState] = React.useState('hello world');
  React.useEffect(() => {
    console.log('useEffect');
    setState('hello useEffect');
  }, []);
  React.useLayoutEffect(() => {
    console.log('useLayoutEffect');
    setState('hello useLayoutEffect');
  }, []);
  return <div>{state}</div>;
}

export default Test;

注意:渲染顺序如下:hello world-->hello useLayoutEffect-->hello useEffect

usedebugvalue

使用实例:

import { useState, useEffect, useDebugValue } from 'react'

const Hook = () => {
  const [ isGone, setIsGone ] = useState(true)
  // 我认为useDebugValue就是用来说明自定义hook标签是做什么的
  useDebugValue('xm isGone', (key) => {
    return `${key}_${new Date().getMinutes()}: ${new Date().getSeconds()}`
  })
  useEffect(() => {
    setIsGone(false)
    return () => {
      setIsGone(true)
    };
  }, [])
  return isGone
}

export default Hook

自定义Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中,本质上只是一种函数代码逻辑的抽取。

自定义 Hook 是一个函数,其名称必须以use开头,函数内部可以调用其他的 Hook。

1、使用公共的userContext

export default function useUserContext() {
  const user = useContext(UserContext);
  const token = useContext(TokenContext);

  return [user, token];
}

import React, { useContext } from 'react';
import useUserContext from '../hooks/user-hook';

export default function CustomContextShareHook() {
  const [user, token] = useUserContext();
  console.log(user, token);
  
  return (
    <div>
      <h2>CustomContextShareHook</h2>
    </div>
  )
}

2、使用公共的数据存储的localStorage

// hooks/local-store-hook.ts
import React,{useState, useEffect} from 'react';
function useLocalStorage(key) {
  const [data, setData] = useState(() => {
    return JSON.parse(window.localStorage.getItem(key))
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(data));
  }, [data]);
  return [data, setData];
}

export default useLocalStorage;
// Customer.tsx
import React, { useState, useEffect } from 'react';

import useLocalStorage from '@/hooks/local-store-hook';

export default function CustomDataStoreHook() {
  const [name, setName] = useLocalStorage("name");

  return (
    <div>
      <h2>CustomDataStoreHook: {name}</h2>
      <button onClick={e => setName("kobe")}>设置name</button>
    </div>
  )
}

Hook规则

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。

function Form() {
  const [name, setName] = useState('Mary');
   // 在条件语句中使用 Hook 违反第一条规则
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }
  const [surname, setSurname] = useState('Poppins');
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });
}
///////////////////////////////////////////////////////////////////////////////////////////////
// 会出现当下的结果
useState('Mary')           // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm)  // 

标签:const,Hooks,React,useState,组件,return,useEffect
来源: https://www.cnblogs.com/yangguanglei/p/16598355.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有