15_Hooks

React函数式组件核心——Hooks

Hooks本质上就是一类特殊的函数,它们可以为你的函数型组件(function component)注入一些特殊的功能。

useState

未使用useState的类式组件的案例

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

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

我们使用hooks后,就变得十分简单

import { useState } from 'react';

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

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

可以看到,Example变成了一个函数,但这个函数却有自己的状态(count),同时它还可以更新自己的状态(setCount)。

useEffect

在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount ,能够在组件挂载完成后执行一些东西

在函数式组件中也可以实现,它采用的是 effectHook ,它的语法更加的简单,同时融合了 componentDidUpdata 生命周期,极大的方便了我们的开发

React.useEffect(() => {    console.log('被调用了');})

由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect 函数,这个函数有多个功能

当我们像上面代码那样使用时,它相当于 componentDidUpdata 和 componentDidMount 一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数

它还可以接收第二个参数,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化

当我们不需要监听任何状态变化的时候,我们可以就传递一个空数组,这样它就能当作componentMidMount 来使用

React.useEffect(() => {    console.log('被调用了');}, [])

这样我们只有在组件第一次挂载的时候触发

当然当页面中有多个数据源时,我们也可以选择个别的数据进行监测以达到我们想要的效果

React.useEffect(() => {    console.log('被调用了');}, [count])

这样,我们就只监视 count 数据的变化

当我们想要在卸载一个组件之前进行一些清除定时器的操作,在类式组件中,我们会调用生命周期钩子 componentDidUnmount 来实现,在函数式组件中,我们的写法更为简单,我们直接在 useEffect 的第一个参数的返回值中实现即可

也就是说,第一个参数的函数体相当于 componentDidMount 返回体相当于 componentDidUnmount ,这样我们就能实现在组件即将被卸载时输出一些东西了

实现卸载

function unmount() {    ReactDOM.unmountComponentAtNode(document.getElementById("root"))}

卸载前输出

React.useEffect(() => {    console.log('被调用了');    return () => {        console.log('我要被卸载了');    }}, [count])

因此 useEffect 相当于三个生命周期钩子,componentDidMountcomponentDidUpdatacomponentDidUnmount

useLayoutEffect

useEffect 很类似

它的作用是:在 DOM 更新完成之后执行某个操作

注意:

  • 有 DOM 操作的副作用 hooks
  • 在 DOM 更新之后执行

执行时机在 useEffect 之前,其他都和 useEffect 都相同

useEffect 执行时机在 render 之后

useLayoutEffect 执行时机在 DOM 更新之后

useMemo

作用:useMemo 类似于Vue的计算属性,监听某个值的变化,根据变化的值重新计算新值;
useMemo会缓存结果,如果监听的值没有发生变化,即使组件重新渲染,也不会重新发生计算,这个行为有助于避免在每个渲染上进行昂贵的计算

注意:优化函数组件中的功能函数

为了避免由于其他状态更新导致的当前函数的被迫执行

第一个参数接收一个函数,第二个参数为数组的依赖列表,返回一个值


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

function Memo (){

    const [count, setCount] = useState(0)
    const [bool, setBool] = useState(true)
    useMemo(() => {
        console.log('变化了')
    }, [count])

    return (
        <div>
            <div>{count}</div>
            <div>{bool ? '真' : '假'}</div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setBool(!bool)}>取反</button>
        </div>
    )
}

export default Memo;

这里讲一下, 以上代码通过点击触发setCount改变count的值,组件会重新渲染且useMemo会监听count值的变化进行重新计算,但是当我i触发setBool改变bool的值,组件只进行了重新渲染但是useMemo没有重新计算并执行,因为当前useMomo监听的时count的变化,其他变化与我无关;

useCallback

useCallback跟useMemo比较类似,但它返回的是缓存的函数。我们看一下最简单的用法

作用:useCallback会将我们传递给它的函数fnB返回,并且将这个结果缓存;当依赖a变更时,会返回新的函数

注意:

  • 只有依赖项改变时才执行
  • useMemo( () => fn, deps) 相当于 useCallback(fn, deps)

不同点:

  • useCallback 返回的是一个函数,不再是值
  • useCallback 缓存的是一个函数,useMemo 缓存的是一个值,如果依赖不更新,返回的永远是缓存的那个函数
  • 给子组件中传递 props 的时候,如果当前组件不更新,不会触发子组件的重新渲染

下面举个例子来说明一下useCallback

import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
 
 
    return <div>
        <h4>{count}</h4>
        <h4>{set.size}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

应用场景:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>

useContext

作用:带着子组件渲染

与类似组件中使用的context类似

注意:

  • 上层数据发生改变,肯定会触发重新渲染
  • 我们需要引入 useContext 和 createContext 两个内容
  • 通过 createContext 创建一个 Context 句柄
  • 通过 Provider 确定数据共享范围
  • 通过 value 来分发数据
  • 在子组件中,通过 useContext 来获取数据
import React, { useContext, createContext } from 'react'
const Context = createContext(null)
export default function Hook() {
    const [num, setNum] = React.useState(1)

    return (
        <h1>
            这是一个函数组件 - {num}
        // 确定范围
            <Context.Provider value={num}>
                <Item1 num={num} />
                <Item2 num={num} />
            </Context.Provider>
        </h1>
    )
}
function Item1() {
    const num = useContext(Context)
    return <div>子组件1  {num}</div>
}
function Item2() {
    const num = useContext(Context)
    return <div>子组件2 {num}</div>
}

useReducer

作用:去其他地方借资源

注意:函数组件的 Redux 的操作

  1. 创建数据仓库 store 和管理者 reducer
  2. 通过 useReducer(store,dispatch) 来获取 statedispatch
const initialState = {
  n: 0,
};
const reducer = (state, action) => {
  if (action.type === "add") {
    /* 规则与useState一样必须返回新的对象,不然变量值不会改变 */
    return { n: state.n + action.number };
  } else if (action.type === "multi") {
    return { n: state.n * action.number };
  } else {
    throw new Error("unknown type!");
  }
};
/* 在函数组件中使用useReducer */
const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const onclick1 = () => {
    dispatch({ type: "add", number: 1 });
  };
  const onclick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  const onclick3 = () => {
    dispatch({ type: "multi", number: 2 });
  };
  return (
    <div className="App">
      <h1>n:{state.n}</h1>
      <button onClick={onclick1}>+1</button>
      <button onClick={onclick2}>+2</button>
      <button onClick={onclick3}>x2</button>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));

通过 dispatch 去派发 action

用法与 useState 类似,从 useReducer 中得到读接口 state,写接口 dispatch。最后操作时传参给 dispatch 写接口。操作灵活多变,比 useState 好处就是能聚集所有的操作和各种状态量。着重理解这几行代码,读懂。

useReducer 算是 useState 的复杂版

使用 useReducer 分以下步骤:

  • 创建初始值的状态initialState
  • 创建所有对状态的操作reducer(state,action)
  • 传给useReducer,得到读和写的接口
  • 调用写({‘type’:‘操作类型’})

自定义Hooks

放在 utils 文件夹中,以 use 开头命名

例如:模拟数据请求的 Hooks

import React, { useState, useEffect } from "react";
function useLoadData() {
  const [num, setNum] = useState(1);
  useEffect(() => {
    setTimeout(() => {
      setNum(2);
    }, 1000);
  }, []);
  return [num, setNum];
}
export default useLoadData;

减少代码耦合

我们希望 reducer 能让每个组件来使用,我们自己写一个 hooks

自定义一个自己的 LocalReducer

import React, { useReducer } from "react";
const store = { num: 1210 };
const reducer = (state, action) => {
  switch (action.type) {
    case "num":
      return { ...state, num: action.num };
    default:
      return { ...state };
  }
};
function useLocalReducer() {
  const [state, dispatch] = useReducer(reducer, store);
  return [state, dispatch];
}
export default useLocalReducer;

自定义Hooks步骤:

  1. 引入 react 和自己需要的 hook
  2. 创建自己的hook函数
  3. 返回一个数组,数组中第一个内容是数据,第二个是修改数据的函数
  4. 暴露自定义 hook 函数出去
  5. 引入自己的业务组件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傅里叶级数ff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值