在 React 项目中全量使用 Hooks的方法

目录
  • 前言
    • React Hooks
    • useState
    • useReducer
      • 基础用法
      • 进阶用法
    • useContext
    • useEffect
    • useLayoutEffect
    • useRef
    • useImperativeHandle
    • useCallback
    • useMemo
  • React Redux Hooks useSelector
    • useDispatch
  • React Router Hooks
    • useHistory
    • useLocation
    • useParams
    • useRouteMatch
  • 参考
  • 结语

前言

此篇文章整理了在 React 项目开发中常用的一些 Hooks

React Hooks

Hooks 只能用于函数组件当中

useState

import { useState } from 'react';

const Component = () => {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>click</button>
  )
}

此方法会返回两个值:当期状态和更新状态的函数。效果同 this.state this.setState,区别是 useState 传入的值并不一定要对象,并且在更新的时候不会把当前的 state 与旧的 state 合并。

useReducer

useReducer 接收两个参数,第一个是 reducer 函数,通过该函数可以更新 state,第二个参数为 state 的初始值,是 useReducer 返回的数组的第一个值,也是在 reducer 函数第一次被调用时传入的一个参数。

基础用法

import { useState } from 'react';

const Component = () => {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>click</button>
  )
}

在基础用法中,返回一个 dispatch 通过 dispatch 触发不同的 action 来加减 state。这里既然能传string action 那么肯定也能传递更复杂的参数来面对更复杂的场景。

进阶用法

import { useReducer } from 'react';

const Component = () => {
  const [userInfo, dispatch] = useReducer(
    (state, { type, payload }) => {
      switch (type) {
        case 'setName':
          return {
            ...state,
            name: payload
          };
        case 'setAge':
          return {
            ...state,
            age: payload
          };
      }
    },
    {
      name: 'Jace',
      age: 18
    }
  );

  return (
    <button onClick={() => dispatch({ type: 'setName', payload: 'John' })}>
      click
    </button>
  );
};

useContext

在上述案例 useReducer 中,我们将函数的参数改为一个对象,分别有typepayload 两个参数,type 用来决定更新什么数据,payload 则是更新的数据。写过 react-redux 的同学可能发这个 reducer 与 react-redux 中的 reducer 很像,我们借助 react-redux 的思想可以实现一个对象部分更改的 reducer ,那么我们便可以使用 React Hooks 的 useContext 来实现一个状态管理。

import { useMemo, createContext, useContext, useReducer } from 'react';

const store = createContext([]);

const App = () => {
  const reducerValue = useReducer(
    (state, { type, payload }) => {
      switch (type) {
        case 'setName':
          return {
            ...state,
            name: payload
          };
        case 'setAge':
          return {
            ...state,
            age: payload
          };
      }
    },
    {
      name: 'Jace',
      age: 18
    }
  );
  const [state, dispatch] = reducerValue;

  const storeValue = useMemo(() => reducerValue, reducerValue);

  return (
    <store.Provider value={storeValue}>
      <Child />
    </store.Provider>
  );
};

const Child = () => {
  const [state, dispatch] = useContext(store); // 在子组件中使用
  console.log(state);
  return (
    <button onClick={() => dispatch({ type: 'setName', payload: 'John' })}>
      click
    </button>
  );
}

useEffect

import { useState, useEffect } from 'react';

let timer = null;

const Component = () => {
  const [count, setCount] = useState(0);

  // 类似于 class 组件的 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    document.title = `You clicked ${count} times`;

    timer = setInterval(() => {
      // events ...
    }, 1000)

    return () => {
      // 类似 componentWillUnmount
      // unmount events ...
      clearInterval(timer); // 组件卸载、useEffect 更新 移除计时器
    };
  }, [count]);

  // ...
}

如果 useEffect 第二个参数数组内的值发生了变化,那么useEffect第一个参数的回调将会被再执行一遍,这里要注意的useEffect 的返回值函数并不只是再组件卸载的时候执行,而是在这个 useEffect 被更新的时候也会调用,例如上述 count 发生变化后,useEffect 返回的方法也会被执行,具体原因见Using the Effect Hook – React (reactjs.org)

useLayoutEffect

useLayoutEffect useEffect 的API相同,区别:useEffect 在浏览器渲染后执行,useLayoutEffect 在浏览器渲染之前执行,由于JS是单线程,所以 useLayoutEffect 还会阻塞浏览器的渲染。区别就是这,那么应用场景肯定是从区别中得到的,useLayoutEffect 在渲染前执行,也就是说我们如果有状态变了需要依据该状态来操作DOM,为了避免状态变化导致组件渲染,然后更新 DOM 后又渲染,给用户肉眼能看到的闪烁,我们可以在这种情况下使用 useLayoutEffect

当然这个不只是状态的改变,在任何导致组件重新渲染,而且又要改变 DOM 的情况下都是 useLayoutEffect 的使用场景。当然这种场景不多,useLayoutEffect 也不能多用,且使用时同步操作时长不能过长,不然会给用户带来明显的卡顿。

useRef

细心的同学有可能发现我在上面写 useEffect 中有一个 timer 变量,我将其定义在了函数组件外面,这样写简单使用是没问题的,但是如果该组件在同一页面有多个实例,那么组件外部的这个变量将会成共用的,会带来一个冲突,所以我们需要一个能在函数组件声明周期内部的变量,可以使用 useState 中的 state 但是 state 发生变化组件也会随之刷新,在有些情况是不需要刷新的,只是想单纯的存一个值,例如计时器的 timer 以及子组件的 Ref 实例等等。

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

const Compnent = () => {
  const timer = useRef(null);
  const [count, setCount] = useState(0);

  useEffect(() => {
    clearInterval(timer.current);
    timer.current = setTimeout(() => {
      setCount(count + 1);
    }, 1000);
  }, [count]);

  return <div>UseRef count: {count}</div>;
}

useRef 只接受一个参数,就是 初始值,之后可以通过赋值 ref.current 来更改,我们可以将一些不影响组件声明周期的参数放在 ref 中,还可以将 ref 直接传递给子组件 子元素。

const ref = useRef();

<div ref={ref}>Hello</div>
// or
<Child ref={ref} />

或许有同学这时候会想到,当子组件为 Class 组件时,ref 获取的是 Class 组件的实例,上面包含 Class 的所有方法属性等。但当子组件为 Function 组件时,ref 能拿到什么,总不可能是 function 内定义的方法、变量。

useImperativeHandle

import React, { useRef, useState, useImperativeHandle } from 'react';

const App = () => {
  const ref = useRef();
  return (
      <Child ref={ref} />
  );
};

const Child = React.forwardRef((props, ref) => {
  const inputRef = useRef();
  const [value, setValue] = useState(1);

  useImperativeHandle(ref, () => ({
    value, // 内部变量
    setValue, // 方法
    input: inputRef.current // Ref
  }));

  return (
    <input value={value} inputRef={inputRef} />
  );
})

使用 useImperativeHandle 钩子可以自定义将子组件中任何的变量,挂载到 ref 上。React.forwardRef 方法可以让组件能接收到 ref ,然后再使用或者透传到更下层。

useCallback

import React, { useCallback } from 'react';

const Component = () => {
  const setUserInfo = payload => {}; // request api

  const updateUserInfo = useCallback(payload => {
    setUserInfo(Object.assign({}, userInfo, payload));
  }, [userInfo]);

  return (
    <UserCard updateUserInfo={updateUserInfo}/>
  )
}

useCallback 会在二个参数的依赖项发生改变后才重新更新,如果将此函数传递到子组件时,每次父组件渲染此函数更新,就会导致子组件也重新渲染,可以通过传递第二个参数以避免一些非必要性的渲染。

useMemo

import React, { useMemo } from 'react';

const Component = () => {
  const [count, setCount] = useState(0);

  const sum = useMemo(() => {
    // 求和逻辑
    return sum;
  }, [count]);

  return <div>{sum}</div>
}

useMemo 的用法跟 useCallback 一样,区别就是一个返回的是缓存的方法,一个返回的是缓存的值。上述如果依赖值 count 不发生变化,计算 sum 的逻辑也就只会执行一次,从而性能。

React Redux Hooks useSelector

import { shallowEqual, useSelector } from 'react-redux';

const Component = () => {
  const userInfo = useSelector(state => state.userInfo, shallowEqual);

  // ...
}

useSelector 的第二个参数是一个比较函数,useSelector 中默认使用的是 === 来判断两次计算的结果是否相同,如果我们返回的是一个对象,那么在 useSelector 中每次调用都会返回一个新对象,所以所以为了减少一些没必要的 re-render,我们可以使用一些比较函数,如 react-redux 自带的 shallowEqual,或者是 Lodash 的 _.isEqual()、Immutable 的比较功能。

useDispatch

import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';

const Compnent = () => {
  const dispatch = useDispatch();
  const clearUserInfo = useCallback(
    () => dispatch({ type: 'clearUserInfo' }),
    [dispatch]
  );

  return (
    <button onClick={clearUserInfo}>click</buttn>
  )
}

使用 dispatch 来调度操作,加上useCallback来减少不必要的渲染。

React Router Hooks

useHistory

import { useHistory } from 'react-router';

const Compnent = () => {
  const history = useHistory();

  return (
    <button onClick={() => history.push('/home')}>go home</buttn>
  )
}

useLocation

import React, { useEffect } from 'react';
import { useLocation } from 'react-router';

const Compnent = () => {
  const location = useLocation();

  useEffect(() => {
    // ...
  }, [location])
}

URL一发生变化,将返回新的 location ,一般可以用来监听 location.search

useParams

import { useParams, useEffect } from 'react-router';

const Component = () => {
  const params = useParams();

  const getUserInfo = id => { // request api
    // some event
  };
  useEffect(() => {
    // parms 的 uid 发生变化就会重新请求用户信息
    getUserInfo(params.uid);
  }, [params.uid]);

  // ...
}

useParams 返回 react-router 的参数键值对

useRouteMatch

import { useRouteMatch } from 'react-router';

const Component = () => {
  const match = useRouteMatch('/login');

  // ...
}

useRouteMatch 可以传入一个参数 path,不传参数则返回当前路由的参数信息,如果传了参数则用来判断当前路由是否能匹配上传递的 path,适用于判断一些全局性组件在不同路由下差异化的展示。

参考

React Hooks

React Redux Hooks

React Router Hooks

结语

使用 Hooks 能为开发提升不少效率,但并不代表就要抛弃 Class Component,依旧还有很多场景我们还得用到它,比如需要封装一个公共的可继承的组件,当然通过自定义 hooks 也能将一些共用的逻辑进行封装,以便再多个组件内共用。

下期更新在React 中自定义 Hooks 的应用场景 ,主要讲一些 Hooks 的高阶应用

到此这篇关于在 React 项目中全量使用 Hooks的文章就介绍到这了,更多相关React使用 Hooks内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React 使用Hooks简化受控组件的状态绑定

    开始之前 阅读本文需要对以下几项有一定了解 ECMAScript 6 文章中大量用到了 ES6 语法,比如解构赋值和函数参数默认值.剩余参数.展开语法.箭头函数等. Hooks React 在 16.8 版本中推出了 Hooks,它允许你在"函数组件"中使用"类组件"的一些特性. React 本身提供了一些 Hooks,比如 useState.useReducer 等.通过在一个以"use"作为命名起始的函数中调用这些 Hooks,就得到了一个

  • React组件学习之Hooks使用

    目录 一.前言 二.React Hooks 2.1 useState 2.2 useEffect 2.3 useMemo 2.4 useCallback 2.5 useContext 2.6 useRef 三.总结 一.前言 react组件分为类(class)组件和函数(function)组件. class 组件是通过继承模版类(Component.PureComponent)的方式开发新组件的,继承是 class 本身的特性,它支持设置 state,会在 state 改变后重新渲染,可以重写一

  • React Hooks使用常见的坑

    React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性.React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态逻辑复用方案,不会产生 JSX 嵌套地狱问题. 为什么会有Hooks? 介绍Hooks之前,首先要给大家说一下React的组件创建方式,一种是类组件,一种是纯函数组件,并且React团队希望,组件不要变成复杂的容器,最好

  • 详解react hooks组件间的传值方式(使用ts)

    目录 父传子 子传父 跨级组件(父传后代) 父传子 通过props传值,使用useState来控制state的状态值 父组件 Father.tsx里: 子组件 Child.tsx里: 展示效果: 子传父 跟react的方式一样,像子组件传入回调函数,通过接收子组件的返回值,再去更新父组件的state 父组件,Father.tsx里: 子组件,Child.tsx里: 展示效果: 子传父优化版,使用useCallback存放处理事件的函数 父组件,Father.tsx里: 子组件,Child.tsx

  • React Hooks的深入理解与使用

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? --拥有了Hooks,生命周期钩子函数可以先丢一边了. 你在还在为组件中的this指向而晕头转向吗? --既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this. 这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张.如果你也对react

  • 详解如何使用React Hooks请求数据并渲染

    前言 在日常的开发中,从服务器端异步获取数据并渲染是相当高频的操作.在以往使用React Class组件的时候,这种操作我们已经很熟悉了,即在Class组件的componentDidMount中通过ajax来获取数据并setState,触发组件更新. 随着Hook的到来,我们可以在一些场景中使用Hook的写法来替代Class的写法.但是Hook中没有setState.componentDidMount等函数,又如何做到从服务器端异步获取数据并渲染呢?本文将会介绍如何使用React的新特性Hook

  • 在 React 项目中全量使用 Hooks的方法

    目录 前言 React Hooks useState useReducer 基础用法 进阶用法 useContext useEffect useLayoutEffect useRef useImperativeHandle useCallback useMemo React Redux Hooks useSelector useDispatch React Router Hooks useHistory useLocation useParams useRouteMatch 参考 结语 前言 此

  • React项目中应用TypeScript的实现

    目录 一.前言 二.使用方式 无状态组件 有状态组件 受控组件 三.总结 一.前言 单独的使用typescript 并不会导致学习成本很高,但是绝大部分前端开发者的项目都是依赖于框架的 例如和vue.react 这些框架结合使用的时候,会有一定的门槛 使用 TypeScript 编写 react 代码,除了需要 typescript 这个库之外,还需要安装@types/react.@types/react-dom npm i @types/react -s npm i @types/react-

  • TypeScript在React项目中的使用实践总结

    序言 本文会侧重于TypeScript(以下简称TS)在项目中与React的结合使用情况,而非TS的基本概念.关于TS的类型查看可以使用在线TS工具

  • 详解React项目中eslint使用百度风格

    1.安装百度Eslint Rule 插件 npm i -D eslint @babel/eslint-parser @babel/eslint-plugin @ecomfe/eslint-config // react项目 npm i -D eslint-plugin-react eslint-plugin-react-hooks // 如果需要支持typescript的话 npm i -D @typescript-eslint/parser @typescript-eslint/eslint-

  • React项目中axios的封装与API接口的管理详解

    目录 前言 安装 引入 环境的切换 请求拦截 响应拦截 api的统一管理 总结 前言 在react项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中.他有很多优秀的特性,例如拦截请求和响应.取消请求.转换json.客户端防御XSRF等.如果还对axios不了解的,可以移步axios文档. 安装 //使用npm安装 npm install axios; //使用yarn安装 yarn add axios 引入 在项目

  • 在React项目中使用TypeScript详情

    目录 项目目录及ts文件划分 在项目中使用TypeScript具体实践 组件声明 React Hooks使用 useState useRef useCallback useMemo useContext useReducer useImperativeHandle Axios请求/响应定义封装 前言: 本文主要记录我如何在React项目中优雅的使用TypeScript,来提高开发效率及项目的健壮性. 项目目录及ts文件划分 由于我在实际项目中大部分是使用umi来进行开发项目,所以使用umi生成的

  • 详解如何优雅地在React项目中使用Redux

    前言 或许你当前的项目还没有到应用Redux的程度,但提前了解一下也没有坏处,本文不会安利大家使用Redux 概念 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与React没有任何关系,其他UI框架也可以使用Redux react-redux React插件,作用:方便在React项目中使用Redux react-thunk 中间件,作用:支持异步action 目录结构 Tips:与Redux无关的目录已省略 |--src |-- store Redux目录

  • 优雅的在React项目中使用Redux的方法

    或许你当前的项目还没有到应用Redux的程度,但提前了解一下也没有坏处 首先我们会用到哪些框架和工具呢? React UI框架 Redux 状态管理工具,与React没有任何关系,其他UI框架也可以使用Redux react-redux React插件,作用:方便在React项目中使用Redux react-thunk 中间件,作用:支持异步action |--src |-- store Redux目录 |-- actions.js |-- index.js |-- reducers.js |-

  • 在react项目中使用antd的form组件,动态设置input框的值

    问题: 创建账号时,输入账号后不搜索直接保存,提示查询后,再点搜索就不能搜索这个账号了 原因: 点击保存之后,对表单进行了验证,导致之后请求的数据无法在更新到input框中,也就是说即使在state中有值,也不会更新initialValue值,就导致搜索后的值不能正确填入input中,表单也就提交不了. 解决办法: 不使用initialValue设置动态更新的值,而是使用 this.props.form.setFieldValue({name:data}); 用于动态更新值,就可以解决了. if

  • react 项目中引入图片的几种方式

    img标签引入图片 因为react其实是通过js的reader函数渲染的页面,所以直接写src="路径"是无法引入图片 我们可以像引入模块一样引入图片 import img from './../../../../asset/img/user.png' 需要用下面的方式引入 <img src={require('../images/picture.png')} alt="标签"/> 背景图片引入 1 第一种就是常规的 新建一个css文件,然后就可以直接写

随机推荐