React Hooks的学习

1、为什么学习hooks,hooks是什么,hooks有什么作用?

Hook 是React16.8的新增特性,它是一个特殊的函数,可以让你“钩入” React 的特性,可以在不用编写class组件的情况下使用state以及其他的React特性。为什么要学习hook呢?因为我们在开发的时候,往往会遇到一些问题,例如:在组件之间复用状态逻辑困难复杂组件变得难以理解难以理解的class等等。Hook 在无需修改组件结构的情况下复用状态逻辑。Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。还可以使用 reducer 来管理组件的内部状态,使其更加可预测。Hook还可以在不使用class的情况下,使用更多的React特性。

2、HOOKS概览

1、 使用 useState

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p> {/*使用State时*/}
      <button onClick={() => setCount(count + 1)}> {/*更新state*/}
        Click me
      </button>
    </div>
  );
}
复制代码

useState 唯一的参数就是初始 state,在例子中就是useState(0),0就是初始state。useState 会返回一对值:当前状态(count)和一个更新它的函数(setCount)。可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。以下是等价的class hook实现的例子:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p> {/*使用State时*/}
        <button onClick={() => this.setState({ count: this.state.count + 1 })}> {/*更新state*/}
          Click me
        </button>
      </div>
    );
  }
}
复制代码

通过以上比较发现:在 class 中,通过在构造函数中设置 this.state{ count: 0 } 来初始化 count state 为 0,显示当前state时,需读取this.state.count,在函数中可直接使用count。更改state时,在 class 中,需要this.setState()来更新 count 值。在函数中,已经有了setCountcount变量,不需要 this

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
复制代码

在函数组件中没有 this,所以不能分配或读取 this.state。可直接在组件中调用 useState Hook,useState Hook可以在一个组件中多次使用,可以同时创建多个变量,在调用useState 时可以给 state 变量取不同的名字:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量
  const [count, setCount] = useState(0);
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
复制代码

注意:调用 useState 方法的时候做了什么? 定义了一个变量count,与 class 里面的 this.state 提供的功能完全相同,一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。useState()里唯一的参数就是初始 state,与class不同的是,不必须使用对象,可以按照需要使用数字或字符串进行赋值。useState 方法的返回值是什么? 就是const [count, setCount] = useState()解构出来的countsetCount这与 class 里面 this.state.count this.setState 类似,唯一区别就是需要成对的获取它们。所以上面是示例可以理解为:声明了一个叫 count 的 state 变量,然后把它设为 0。React 会在重复渲染时记住它当前的值,并且提供最新的值给setCount函数,通过调用 setCount更新当前count

2、使用 useEffect

在熟悉 React class 的生命周期函数后,可以将 useEffect Hook 看做 componentDidMount componentDidUpdatecomponentWillUnmount 这三个函数的组合。与 componentDidMountcomponentDidUpdate 不同的是,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,使应用看起来响应更快。 大多数情况下,effect 不需要同步地执行。useEffect可以在函数组件中执行副作用操作(数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用)以下是分别是class组件/函数组件 例子:

//class组件
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
//函数组件
import React, { useState, useEffect } from 'react';

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

  // 与componentDidMount和componentDidUpdate类似:
  useEffect(() => {
    // 使用浏览器API更新文档标题
    document.title = `You clicked ${count} times`;
  });

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

注意: 在 class 组件中,要在两个生命周期函数中编写重复的代码。而使用useEffect,告诉了组件在渲染后执行某些操作,将其保存,在执行 DOM 更新之后调用它。为什么在函数组件内部调用呢?因为将 useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props),Hook 使用了 JavaScript 的闭包机制,不用引入特定的 React API来读取它。默认情况下,useEffect在第一次渲染之后和每次更新之后都会执行

将 useEffect Hook 看做 componentDidMount componentDidUpdatecomponentWillUnmount 这三个函数的组合的写法如下:当返回一个函数时,即做了清除操作。更新逻辑不需要特定的代码来处理,因为 useEffect 默认就会处理,它会在调用一个新的 effect 之前对前一个 effect 进行清理。可使用多个effect,将不相关逻辑分离到不同的 effect 中,

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => { //清除操作
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
//性能优化
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
复制代码

3、使用useContext

const value = useContext(MyContext);接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值,当前值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定,当组件上最近的<MyContext.Provider>更新时,useContext会触发重渲染,并使用最新值传递给 MyContext providercontext value 值,如以下示例:

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
const ThemeContext = React.createContext(themes.light);
function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}
function Toolbar() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}
复制代码

4、使用useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)useState的替代方案,当state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 时,useReducer 会比 useState 更适用,且使用 useReducer 还能给那些会触发深更新的组件做性能优化,以下是用 reducer 重写 useState 例子:

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  //第三个参数,用作惰性初始化
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
复制代码

5、使用useCallback

内联回调函数依赖项数组作为参数传入 useCallback,返回该函数的当前版本,该回调函数仅在某个依赖项改变时才会更新

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);
复制代码

注意:依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。

6、使用useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])创建函数依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。传入 useMemo 的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作,如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值,可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证,useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

6、使用useRef

const refContainer = useRef(initialValue)useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。useRef() 比 ref 属性更有用,它可以很方便地保存任何可变值,因为ref 属性创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

function TextInputWithFocusButton() {
  const myRef = useRef(null); // null为初始值
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    myRef.current.focus();
  };
  return (
    <>
      <input ref={myRef} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
复制代码

7、使用useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])useImperativeHandle 可以在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
// 渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()。
复制代码

8、使用useLayoutEffect

函数形参与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

9、使用useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。例如:一个返回 Date 值的自定义 Hook 可以通过格式化函数来避免不必要的 toDateString 函数调用:
useDebugValue(date, date => date.toDateString());

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享