React学习笔记(八)--- HooK

一、简介

​ 在之前的学习中,我们了解到只有类组件中才能使用 state,函数组件是无法使用的。但Hook的出现,改变这种情况,Hook是React 16.8 版本的新增特性,可以让我们在函数组件中使用state、生命周期以及refs、context等其他相关特性。而且Hook可以让我们在不更改组件结构的前提下复用状态逻辑,使得在组件间共享Hook变得十分便利。

​ 简单来说,Hook就是一些React中封装好的state以及生命周期、refs等特性的方法,本质就是 JavaScript 函数。除了React为我们准备好的Hook,我们还可以自己创建自定义Hook。

​ 现在React在企业中的开发模式通常为:Hook+函数组件的开发模式。

​ 官网文档: https://zh-hans.reactjs.org/docs/hooks-intro.html

二、使用规则

1、Hook不能在类组件中使用,只能在函数组件以及自定义Hook中使用。
2、只能在函数组件的最外层调用Hook,不能在循环、判断或子函数中调用Hook。

​ 通常我们会把Hook写在组件内的最前面,这样才能确保 Hook 在每一次渲染中都按照同样的顺序被调用。让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确,因为React是通过 Hook 的调用顺序来明确 state 和 useState 的对应关系。

三、常用Hook

1、State Hook(useState)

​ State Hook 是用来创建 state 的Hook,可以通过在函数组件中调用 useState 方法来给组件设置 state。该方法只有一个参数,表示创建的 state 的初始值,这个初始值只有在第一次渲染的时候才会被用到。我们通过数组解构来获取方法的返回值,返回值有两个,第一个返回值为所创建的 state,第二个返回值为更新所创建的 state 的函数,类似于 setState,函数名为:set + state的名字(首字母大写)。想要更新创建的state,只能通过该更新函数来更新,函数的参数就是更新后变量的值。

// 利用数组解构 获取 count 变量 和更新函数setCount 
// 并设置初始值为 0
  const [count, setCount] = useState(0);

​ 如果想要在一个函数组件中声明多个 state 变量,只需要多次调用 useState 方法即可,但 state 变量名不能重复。

综合案例:
Hook+函数组件:
// 1、导入 useState 
import React, { useState } from 'react';

function Example() {
  // 2、声明一个叫 "count" 的 state 变量  并声明初始值为 0
  // 利用数组解构 获取 count 变量 和更新函数setCount
  const [count, setCount] = useState(0);
  // 声明多个变量
  const [Uname, setUname] = useState('猪猪侠');
  const [info, setInfo] = useState({age: 18,height: '180cm'});

  return (
    
{/* 3、调用变量 */}

{You} clicked {count} times

{/* 4、调用更新函数 */}
); }
等价的类组件:
class Example extends React.Component {
  constructor(props) {
    super(props);
      // 声明并初始化 state
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      

You clicked {this.state.count} times

); }

2、Effect Hook(useEffect)

​ 在React官方文档中,把数据获取、操作DOM、订阅数据、调用浏览器API等操作,称为副作用(真tn抽象,鬼才翻译!)。而Effect Hook 就是通过 useEffect 来给函数组件提供操作这些副作用的能力的方法,作用相当于类组件的生命周期钩子函数(componentDidMountcomponentDidUpdatecomponentWillUnmount),只不过将这多个函数合并构成了一个Hook。而且与这些钩子函数不同,使用 useEffect 不会阻塞浏览器更新屏幕,属于异步执行,这让应用看起来响应更快。

// 使用 useEffect 修改页面标题
useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

​ 组件中可以多次调用 useEffect ,对不同的副作用进行操作,将不相关的逻辑分离到不同的effect中。React将按照顺序依次调用组件中的effect。

​ useEffect 有两个参数,第一个参数是一个回调函数,表示在React完成DOM更新渲染操作后再运行该函数,函数内对副作用进行操作,还可以访问到当前组件的 props 和 state。第二个参数是一个数组(可选),决定在何时触发 useEffect ,减少无效渲染,优化性能。只有当数组内元素的值发生变化后,才会触发对应的effect。默认情况下,每次页面 DOM 更新后都会触发 useEffect,包括页面的第一次渲染 。

​ 如果只想在组件挂载和卸载时,运行 effect,只需要将 useEffect 的参数设置为 [] 空数组即可。相当于告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

// 只有 count 变量变化后 才会使用 useEffect 修改页面标题  
useEffect(() => {
    document.title = `You clicked ${count} times`;
  },[count]);
// 只在挂载和卸载时 执行一次
useEffect(() => {
    document.title = `You clicked times`;
  },[]);

​ useEffect 还可以通过地那一个参数回调函数 return 返回一个函数,该函数表示如何清除设置的副作用,有些副作用是无需清除的,而有些副作用谁需要清除的,例如:事件解绑、取消订阅等。effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次,调用一个新的 effect 之前对前一个 effect 进行清理。每次我们重新渲染,都会生成新的 effect,替换掉之前的。

综合案例:
Hook+函数组件:
// 1、导入 Hook
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
    // 2、创建变量
  const [isOnline, setIsOnline] = useState(null);
·// 3、创建 effect
  useEffect(() => {
      // 在DOM渲染完之后 执行的代码
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // return 一个在销毁时执行的消除函数
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
      // 只有当 props.friend.id 更新时才会触发
  },[[props.friend.id]]);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
等价的类组件:
class FriendStatus extends React.Component {
    // 1、创建变量
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }
// 2、在不同的生命周期函数执行类似的代码 代码不简洁
  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

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

四、自定义Hook

​ 之前我们学习的React中组件之间共享状态逻辑的方法有两种:render props 和 高阶组件。现在我们也可以通过自定义Hook来实现函数组件间共享状态逻辑。

​ 自定义 Hook 就是一个函数,但其名称必须以 “use” 开头,而且函数内部可以调用其他的 Hook。我们不需要对共享的状态逻辑做改变,只是将两个函数之间一些共同的代码提取到单独的函数中。所以自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。

​ 如果多个组件之间使用相同的自定义Hook,并不会共享 state 变量,只是复用了逻辑,每次使用自定义 Hook 时,其中的所有 state 和副作用操作都是完全隔离的。

​ 如果我们在调用自定义Hook时,我们向Hook传递一个变量,Hook给我们返回一个值,那么每次变量更新后,React都会再次调用这个自定义Hook,获取最新的返回值。

案例代码:

自定义的Hook:

// 引入系统Hook
import { useState, useEffect } from 'react';
// 自定义Hook 接受一个参数
function useFriendStatus(friendID) {
    // 创建变量
  const [isOnline, setIsOnline] = useState(null);
   
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });
// 返回值
  return isOnline;
}

在其他组件中调用:

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
    // 使用自定义 Hook 每次recipientID变化 都会调用
  const isRecipientOnline = useFriendStatus(recipientID);
	
  return (...);
}

五、其他内置Hook

1、useContext

语法: const value = useContext(MyContext);

useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 。接收一个context对象为参数,并返回该context的当前值, 值由上层组件中距离当前组件最近的 value prop 决定。当组件上层最近的 更新时,该 Hook 会触发重渲染,并返回最新值。调用了 useContext 的组件总会在 context 值变化时重新渲染。

案例代码:
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

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

function App() {
  return (
    
      
    
  );
}

function Toolbar(props) {
  return (
    
); } function ThemedButton() { const theme = useContext(ThemeContext); return ( ); }

2、userRef

语法:const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。它与useImperativeHandle 结合使用,可以在父组件中访问子组件通过useImperativeHandle 暴露的数据。本质上,useRef 就像是在其 .current 属性中保存一个可变值的“盒子”,而这个盒子的值,由对应 ref 的子组件决定,也可以对对应的DOM元素进行渲染。

useRef 会在每次渲染时返回同一个 ref 对象,返回的 ref 对象在组件的整个生命周期内持续存在。但是变更 .current 属性不会引发组件重新渲染。

案例代码:
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      
      
    
  );
}

3、useImperativeHandle

语法:useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

案例代码:
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return ;
}

4、useLayoutEffect

​ 该Hook与useEffect功能相同,只不过useEffect是异步的,而useLayoutEffect会在所有的 DOM 变更之后同步调用 effect,会在一定程度上阻塞视觉刷新。可以使用它来读取 DOM 布局并同步触发重渲染。

5、useCallback

语法:
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback可以返回一个 memoized 回调函数,把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

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

6、useMemo

语法: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo可以返回一个 memoized 值,把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

​ 传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作

7、useReducer

语法: const [state, dispatch] = useReducer(reducer, initialArg, init);

useState的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的修改对应值的 dispatch 方法。在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为[你可以向子组件传递 dispatch 而不是回调函数 。

useState的第一个参数是对应renducer方法,第二个参数用来指定初始值,第三个参数用来惰性初始化初始值。第二个参数设置初始值时,只需要传入一个对象,对象中存储属性和值。第三个参数惰性初始化,则需要声明一个init函数,通过这个函数来设置初始值,这样初始 state 将被设置为 init(initialArg)

// init函数
function init(initialCount) {
  return {count: initialCount};
}
	// 惰性初始化
 const [state, dispatch] = useReducer(reducer, initialCount, init);
案例代码:
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {count: 0});
  return (
    <>
      Count: {state.count}
      
      
    
  );
}

8、useDebugValue

语法:useDebugValue(value)

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

你可能感兴趣的:(React,react.js,学习,javascript)