React18笔记:常用hooks及其实践问题解决

一、常用hooks

Hooks —— 以 use 开头的函数,只能在组件或自定义 Hook 的最顶层调用。 不能在条件语句、循环语句或其他嵌套函数内调用 Hook。Hook 是函数,在组件顶部 “use” React 特性,类似于在文件顶部“导入”模块。

Hook描述参考文档
useState用于创建状态变量useState hook
useEffect用于副作用操作,比如网络请求、设置订阅、手动修改 DOM 等useEffect hook
useLayoutEffectuseLayoutEffect 是 useEffect 的一个版本,的在浏览器重新绘制屏幕之前同步执行,会阻塞浏览器绘制页面useLayoutEffect hook
useContext用于共享数据,类似于 React.contextuseContext hook
useReducer用于管理组件本地状态,相当于 class 组件中的 this.setStateuseReducer hook
useCallback用于缓存函数,相当于 class 组件中的 memoizationuseCallback hook
useMemo用于缓存值,相当于 class 组件中的 PureComponentuseMemo hook
useRef用于获取 DOM 元素或创建自定义的 refsuseRef hook
useDebugValue用于在 React DevTools 中自定义 Hook 的显示名称useDebugValue hook

二、FAQ

1、使用三元运算符条件显示自定义组件useEffect不触发?

自定义组件Button:

// src/components/Button.tsx
import {useEffect, useRef} from 'react';

type ButtonProps = {
    onClick?: () => void;
    text?: string;
}

function Button(props: ButtonProps) {
    console.log('button render', props);
    const interval = useRef<NodeJS.Timer | null>(null);

    useEffect(() => {
        console.log(`button ${props.text} mounted`);
        props.text === '1' && startTimer();
        return () => unMounted();
    }, []);

    function unMounted() {
        console.log(`button ${props.text} unmounted`);
        interval.current && clearInterval(interval.current);
    }

    function startTimer() {
        interval.current = setInterval(() => {
            console.log('timer', props.text);
        }, 1000);
    }

    return (
      <div>
          <button onClick={props.onClick}>{props.text}</button>
      </div>
    );
}

export default Button;

使用自定义组件Button:三元运算条件显示,Button 1 隐藏不会触发 button 1 unmountedbutton 2 mounted

// src/App.tsx
import {useState} from 'react';
import Button from './components/Button'
import './App.less';

type TactionMap = {
  [key: string]: () => void;
}

function App() {
  const [isVisible, setIsVisible] = useState(true);

  const handleButtonClick = () => {
    console.log('button click');
    setIsVisible(!isVisible);
  }

  return (
    <div className="App">
      <div className='App-desc'>hello Button</div>
      {isVisible ? <Button text='1' onClick={handleButtonClick} /> : <Button text='2' onClick={handleButtonClick} />}
    </div>
  );
}

export default App;

条件 && 运算符显示,useEffect 触发, 能正常打印button 1 unmountedbutton 2 mounted

// src/App.tsx
import {useState} from 'react';
import Button from './components/Button'
import './App.less';

type TactionMap = {
  [key: string]: () => void;
}

function App() {
  const [isVisible, setIsVisible] = useState(true);

  const handleButtonClick = () => {
    console.log('button click');
    setIsVisible(!isVisible);
  }

  return (
    <div className="App">
      <div className='App-desc'>hello Button</div>
      {isVisible && <Button text='1' onClick={handleButtonClick} />}
      {!isVisible && <Button text='2' onClick={handleButtonClick} />}
    </div>
  );
}

export default App;

或者给 ButtonuseEffect 添加指定依赖项 props.text,就能正常触发 button 1 unmountedbutton 2 mounted

// src/components/Button.tsx
...
useEffect(() => {
    console.log(`button ${props.text} mounted`);
    props.text === '1' && startTimer();
    return () => unMounted();
}, [props.text]);
...

2、useEffect、useLayoutEffect的区别?

执行时机

useEffect 中的副作用操作是在浏览器完成渲染后异步执行的,即它不会阻塞浏览器的渲染过程。

useLayoutEffect 中的副作用操作是在浏览器完成渲染、但在浏览器布局和绘制之前同步执行的,即它会在浏览器更新 DOM 之前立即执行

性能影响

由于 useEffect 中的副作用操作是异步执行的,因此它不会阻塞浏览器的渲染过程,对页面性能影响较小

useLayoutEffect 中的副作用操作是同步执行的,并且会阻塞浏览器的渲染过程,因此应该谨慎使用,避免影响页面性能

适用场景

useEffect 适用于大多数副作用操作,特别是那些不需要立即同步执行的操作,比如数据获取、订阅事件等。

useLayoutEffect 适用于那些需要在浏览器完成渲染之前立即执行的副作用操作,比如手动操作 DOM、测量元素尺寸等。

一般来说,如果副作用操作不需要在浏览器布局和绘制之前立即执行,推荐使用 useEffect;如果副作用操作需要在浏览器布局和绘制之前立即执行,才能达到预期的效果,那么可以考虑使用 useLayoutEffect。

3、memo、useMemo、useCallback的区别?怎么用?

useMemo、useCallback 只对拥有记忆功能的组件有效,及必须搭配memo使用。

不必要的渲染开销?

react 组件在更新时,会重新渲染整个组件树。当父组件调用了 setState 触发更新时,会重新渲染所有的子组件,即使子组件的 props 没有改变。

因此,当组件渲染开销较大时,会带来性能问题。

避免不必要的渲染?
1. memo 缓存组件

为了解决这个问题,可以使用 memo 组件来避免不必要的渲染。

memo 组件是 React 的一个高阶组件,它接收一个函数作为参数,返回一个新的函数。这个新函数会根据 props 的值使用 Object.is() 进行浅层判断,如果 props 没有变化,则返回缓存的组件。

使用例子:

import {memo} from 'react';

type ButtonProps = {
    onClick?: () => void;
    text?: string;
}

function Button(props: ButtonProps) {
    console.log('button render', props);
    // ...
    return (
        <div>
            <button onClick={props.onClick}>{props.text}</button>
        </div>
    );
}

export default memo(Button);
2. useMemo 缓存函数

memo 只能缓存组件,不能缓存函数。当属性是函数时,依然会触发子组件的重新渲染。useMemo 是一个 React Hook,它接收一个函数作为参数,返回一个新的记忆函数。

语法:

// 当依赖项没有变化时,useMemo 返回的函数不会被调用
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

使用例子:

// src/App.tsx

import {useState, useMemo} from 'react';
import Button from './components/Button';
import './App.less';

function App() {
  const [count, setCount] = useState(0);
  const [isVisible, setIsVisible] = useState(true);

  // 只有setIsVisible变化时,才会重新计算handleButtonClick,触发Button重新渲染
  // 其他地方调用setCount时,不会触发Button重新渲染
  const handleButtonClick = useMemo(() => {
    return () => {
      console.log('button click');
      setIsVisible(!isVisible);
    }
  }, [isVisible]);

  return (
    <div className="App">
      <div className='App-desc'>hello Button</div>
      {isVisible ? <Button text='1' onClick={handleButtonClick} /> : <Button text='2' onClick={handleButtonClick} />}
    </div>
  );
}

export default App;

useMemo 更适合用来缓存值,而不是函数。

// src/App.tsx
import {useState, useMemo} from 'react';
import Button from './components/Button';

function App() {
  const [info, setInfo] = useState({
    a: 1,
    b: 10
  });

  // 1. 计算值
  const getSum = useMemo(() => {
    // info 变化时,getSum 重新计算
    console.log('getSum', info);
    // 只有当计算结果发生变化时,才会触发子组件的重新渲染
    return info.a + info.b + '';
  }, [info]);

  // 不会触发子组件的重新渲染
  const handleNoChangeInfo = () => {
    setInfo({
      a: info.b + 1,
      b: info.a - 1
    })
  }

  // 会触发子组件的重新渲染
  const handleChangeInfo = () => {
    setInfo({
      a: info.b + 1,
      b: info.a + 1
    })
  }


  return (
    <div className="App">
      <div className="App-item-btns">
          <Button text={getSum}/>
          <button onClick={handleNoChangeInfo}>no change info</button>
          <button onClick={handleChangeInfo}>change info</button>
        </div>
      </div>
  );
}

export default App;
3. useCallback 缓存函数

useCallback 也是一个 React Hook,它接收两个参数:函数和依赖项。当依赖项发生变化时,返回一个新的函数。

语法:

const memoizedCallback = useCallback(fn, [dep1, dep2]);

使用例子:

// src/App.tsx
import {useState, useCallback} from 'react';
import Button from './components/Button';
function App() {
  const [count, setCount] = useState(0);
  const [isVisible, setIsVisible] = useState(true);

  // 只有setIsVisible变化时,才会重新计算handleButtonClick,触发Button重新渲染
  // 其他地方调用setCount时,不会触发Button重新渲染
  const handleButtonClick = useCallback(() => {
    console.log('button click', isVisible);
    setIsVisible(!isVisible);
  }, [isVisible]);

return (
    <div className="App">
      <div className='App-desc'>hello Button</div>
      {isVisible ? <Button text='1' onClick={handleButtonClick} /> : <Button text='2' onClick={handleButtonClick} />}
    </div>
  );
}

export default App;
总结

1、useMemo 更适合缓存值,useCallback 用来缓存函数
2、useMemouseCallback 必须配合 memo 一起使用
3、一般只在渲染性能开销大的组件中缓存策略

4、如何在现有Ract项目中使用TypeScript?

1. 安装TypeScript
npm install --save-dev typescript @types/react @types/react-dom
# 或者
yarn add --dev typescript @types/react @types/react-dom
2. 配置tsconfig.json文件
{
    "compilerOptions": {
      "target": "es5",
      "lib": [
        "dom",
        "dom.iterable",
        "esnext"
      ],
      "allowJs": true,
      "skipLibCheck": true,
      "esModuleInterop": true,
      "allowSyntheticDefaultImports": true,
      "strict": true,
      "forceConsistentCasingInFileNames": true,
      "noFallthroughCasesInSwitch": true,
      "module": "esnext",
      "moduleResolution": "node",
      "resolveJsonModule": true,
      "isolatedModules": true,
      "noEmit": true,
      "jsx": "react-jsx"
    },
    "include": [
      "src"
    ]
  }

5、Create React App 项目解决前端请求跨域?

package.json 中添加 proxy 配置:

// 改成你要请求的地址就行
"proxy": "http://localhost:3001",

请求例子:

import { useEffect, useState } from "react";

export interface IParams {
  keyword?: string;
  tag?: string;
  region?: string;
  center?: string;
  scope?: number;
}

function useSearchPoiList() {
  const [poiList, setPoiList] = useState<any[]>([]);

  useEffect(() => {
    getPoiList();
  }, []);

  const getUrl = (params: IParams) => {
    return `/place/v2/search?query=${params.keyword}&tag=${params.tag}&region=${params.region}&output=json&ak=xxx&center=${params.center}&scope=${params.scope}`;
  };

  const getPoiList = (params?: IParams) => {
    fetch(
      getUrl({
        keyword: "美食",
        tag: "美食",
        region: "禅意小镇·拈花湾",
        center: "120.968375,24.09286",
        scope: 2,
        ...params,
      })
    )
      .then((res) => res.json())
      .then((data: any) => {
        setPoiList(data.results || []);
        console.log("getPoiList data", data.results || []);
      })
      .catch((error) => {
        setPoiList([]);
        console.log("getPoiList error", error);
      });
  };

  return {
    poiList,
    getPoiList,
  };
}

export default useSearchPoiList;

6、怎么在React项目中使用less?

方法思路:覆盖默认的 webpack 配置

1. 安装以下依赖

用来编译 less 文件和覆盖 webpack 配置的依赖:

npm install --save-dev customize-cra customize-cra-less-loader less less-loader react-app-rewired
# 或者
yarn add --dev customize-cra customize-cra-less-loader less less-loader react-app-rewired
2. 添加 config-overrides.js 文件

在 config-overrides.js 中定义自定义配置:

//config-overrides.js
const { override } = require('customize-cra');
const addLessLoader = require("customize-cra-less-loader");

module.exports = {
  webpack: override(
    addLessLoader({
      cssLoaderOptions: {
        modules: {
          localIdentName: '[file]',
        }, 
      },
      // less loader options
      lessLoaderOptions: {
        lessOptions: {
          strictMath: true,
        }
      }
    }),
  )
}
3. 在 package.json 中修改启动方式

修改启动方式,使用 react-app-rewired 启动项目:

"scripts": {  
  "start": "react-app-rewired start",  
  "build": "react-app-rewired build",  
  "test": "react-app-rewired test",  
  "eject": "react-scripts eject"  
}

重启项目就可以了

7、使用useReducer搭建数据仓库?自定义hook实现异步数据共享?

使用 useReducer + 自定义hook,实现一个简易数据仓库代替方案,可以用来管理全局状态和共享逻辑。

1. 定义数据仓库
// src/store/countStore.ts
export const initialCount = {
    count: 0,
};
  
export type InitialCount = typeof initialCount;
export type TIncrement = { type: "increment" };
export type TDecrement = { type: "decrement" };
export type TReset = { type: "reset"; payload: InitialCount};
export type ACTIONTYPE = TIncrement | TDecrement | TReset;

const countStore = (state: InitialCount, action: ACTIONTYPE) => {
    switch (action.type) {
        case "increment":
            return { count: state.count + 1 };
        case "decrement":
            return { count: state.count - 1 };
        case "reset":
            return action.payload;
        default:
            throw new Error("No such type");
    }
}

export default countStore;
2. 在页面中使用数据仓库
// src/App.tsx
import { useReducer } from 'react';
import countStore, {initialCount} from './store/countStore';

function App() {
  // 引入多个数据仓库,可以结构重命名state和dispatch方法
  // const [other, otherDispatcher] = useReducer(otherStoreReducer, otherinitial);
  const [state, dispatch] = useReducer(countStore, initialCount);

  const handleChangeStore = (e: React.MouseEvent<HTMLButtonElement>) => {
    const type: string = e.currentTarget.getAttribute('datatype') || 'add';
    const actionMap: TactionMap = {
      increment: () => dispatch({type: 'increment'}),
      decrement: () => dispatch({type: 'decrement'}),
      reset: () => dispatch({type: 'reset', payload: initialCount})
    }
    actionMap[type]();
    console.log('state.count', state.count);
  }

  return (
    <div className="App">
      <h1>count: {state.count}</h1>
      <button datatype="increment" onClick={handleChangeStore}>increce</button>
      <button datatype="decrement" onClick={handleChangeStore}>decrece</button>
      <button datatype="reset" onClick={handleChangeStore}>reset</button>
    </div>
  );
}
3. 自定义hook实现异步数据共享
// src/hooks/useSearchPoiList.tsx
import { useEffect, useState } from "react";

export interface IParams {
  keyword?: string;
  tag?: string;
  region?: string;
  center?: string;
  scope?: number;
}

function useSearchPoiList() {
  const [poiList, setPoiList] = useState<any[]>([]);

  useEffect(() => {
    getPoiList();
  }, []);

  const getUrl = (params: IParams) => {
    return `/place/v2/search?query=${params.keyword}&tag=${params.tag}&region=${params.region}&output=json&ak=xxx&center=${params.center}&scope=${params.scope}`;
  };

  const getPoiList = (params?: IParams) => {
    fetch(
      getUrl({
        keyword: "美食",
        tag: "美食",
        region: "禅意小镇·拈花湾",
        center: "120.968375,24.09286",
        scope: 2,
        ...params,
      })
    )
      .then((res) => res.json())
      .then((data: any) => {
        setPoiList(data.results || []);
        console.log("getPoiList data", data.results || []);
      })
      .catch((error) => {
        setPoiList([]);
        console.log("getPoiList error", error);
      });
  };

  return {
    poiList,
    getPoiList,
  };
}

export default useSearchPoiList;
4. 在页面中使用自定义hook
// src/App.tsx
import useSearchPoiList from './hooks/useSearchPoiList';

function App() {
  // 使用自定义hook
  const {poiList, getPoiList} = useSearchPoiList();

  const handleConfirm = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.code === 'Enter') {
      getPoiList({
        keyword: e.currentTarget.value
      });
      console.log('handleConfirm', e.currentTarget.value, poiList);
    }
  }

  return (
    <div className="App">
      <div className="App-item">
        <div>change keword</div>
        <input defaultValue='美食' onKeyDown={handleConfirm} />
      </div>
      <div>poiList</div>
      {
        poiList.map((item: any, index) => {
          return (
            <div 
              key={index}
              style={{
                border: '1px solid #ccc',
                padding: '10px',
                margin: '10px',
              }}>
              <h3 key={index}>{item.name}</h3>
              <p>{item.address}</p>
            </div>
          )
        })
      }
    </div>
  );
}

持续更新中…

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
React 18全家桶是React.js的最新版本,它引入了很多新特性和改进,使得开发更加方便和高效。其中,Hooks是React 16.8版本引入的一项重要特性,它允许我们在无需编写类组件的情况下,在函数式组件中使用状态和其他React功能。 React 18全家桶Hooks项目实战网盘是一个基于React 18全家桶的实际开发项目,旨在通过实际开发来学习和掌握React 18全家桶及Hooks的使用。 在这个项目中,我们可以通过网盘将文件上传、下载和分享给其他用户。首先,我们可以使用React 18全家桶的新特性来创建一个界面,例如使用React函数组件和Hooks来管理界面的状态、处理用户输入以及渲染文件列表等。我们可以使用React Router来管理不同页面之间的导航,并使用React Context来共享全局的状态和数据。 对于文件的上传和下载功能,我们可以利用React 18全家桶提供的新API,例如使用React Concurrent Mode来提高文件上传和下载的性能。同时,我们可以使用React Query来管理文件的后台数据请求和状态更新,以及使用React Hook Form来处理表单的数据验证和提交。 在项目中,我们可以使用React 18全家桶的新特性来实现一些高级的功能,例如使用React Server Components来实现服务器端渲染和实时数据更新,或使用React Fast Refresh来提高开发和调试的效率。 通过参与React 18全家桶Hooks项目实战网盘,我们可以深入了解并熟练掌握React 18全家桶及Hooks的使用。这将有助于我们在实际的React项目开发中提高开发效率和代码质量。同时,我们也可以通过参与项目来拓展我们的React技术栈,并与其他开发者共同学习和交流。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值