React Hooks

React Hooks :组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。

 

性能比较:在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别。

 

Hooks的优势:

  • Hooks 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理的成本(但是不可避免的使用了闭包)。
  • Hooks的组件写法可以更多的把逻辑抽离出来,与class相比减少了模板代码,拆分的粒度更细,复用性更高(最大的优点),高阶组件和renderProps也同样能做到。但hooks实现起来的代码量更少,以及更直观(代码可读性)。
  • 避免了在class写法中需要在不同生命周期写重复逻辑的心智负担。
  • 无需考虑this的指向问题。

 

目前,我通常更偏向于用hooks来写组件,但在复杂业务中,我会更倾向于用class Component或者两者结合的方式。hooks会是未来的主流组件编写方式,但目前来说它还不成熟。

 

常用的Hooks:

import react, { useState, useEffect, useLayoutEffect, useRef } from "react";

// useState : 代替class组件的内部state
const [data, setData] = useState({});

// useEffect : 代替生命周期 componentDidMount,componentDidUpdate
useEffect(() => {
  const handle = () => {
    console.log("输出===");
  };
  window.addEventListener("reload", handle());
  return () => {
    window.removeEventListener("reload");
  }; // 代替生命周期 componentWillUmout
}, []); // [] 数组传入比较的值,类似于pureComponent

// useLayoutEffect : 使用方式与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。

// useRef : 返回一个可变的 ref 对象(在组件的整个生命周期内保持不变,可用于初始化定时器),其 .current 属性被初始化为传入的参数。
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

// useCallback,把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。(例如 shouldComponentUpdate)
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b] // 依赖数据 a,b改变时才会出发回调函数
);

// useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

 

useMemo和useCallback的区别

相同点:

  • useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。
  • useMemo、useCallback 都是使参数(函数)不会因为其他不相关的参数变化而重新渲染。
  • 与 useEffect 类似,[] 内可以放入你改变数值就重新渲染参数(函数)的对象。如果 [] 为空就是只渲染一次,之后都不会渲染。

区别:

  • 主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

 

useMemo使用场景

不使用useMemo:

function Example() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function getNum() {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }
 
    return <div>
        <h4>总和:{getNum()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

上面这个组件,维护了两个 state ,可以看到 getNum 的计算仅仅跟 count 有关,但是现在无论是 count 还是 val 变化,都会导致 getNum 重新计算,所以这里我们希望 val 修改的时候,不需要再次计算,这种情况下我们可以使用 useMemo 

使用useMemo:

function Example() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    const getNum = useMemo(() => {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }, [count])
 
    return <div>
        <h4>总和:{getNum()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

使用 useMemo 后,并将 count 作为依赖值传递进去,此时仅当 count 变化时才会重新执行 getNum 

 

useCallback的使用场景:

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

function Parent() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    const getNum = useCallback(() => {
        return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
    }, [count])
 
    return <div>
        <Child getNum={getNum} />
        <div>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}
 
const Child = React.memo(function ({ getNum }: any) {
    return <h4>总和:{getNum()}</h4>
})

使用 useCallback 之后,仅当 count 发生变化时 Child 组件才会重新渲染,而 val 变化时,Child 组件是不会重新渲染的。

 

解决状态不同步:


函数的运行是独立的,每个函数都有一份独立的作用域。函数的变量是保存在运行时的作用域里面,当我们有异步操作的时候,经常会碰到异步回调的变量引用是之前的,也就是旧的(这里也可以理解成闭包)如下:

import React, { useState } from "react";
​
const Counter = () => {
  const [counter, setCounter] = useState(0);
​
  const onAlertButtonClick = () => {
    setTimeout(() => {
      alert("Value: " + counter);
    }, 3000);
  };
​
  return (
    <div>
      <p>You clicked {counter} times.</p>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      <button onClick={onAlertButtonClick}>
        Show me the value in 3 seconds
      </button>
    </div>
  );
};
​
export default Counter;

当你点击Show me the value in 3 seconds的后,紧接着点击Click me使得counter的值从0变成1。三秒后,定时器触发,但alert出来的是0(旧值),但我希望的结果是当前的状态1。
这时我们可以用useEffect来实现我们的需求

import React, { useState, useRef, useEffect } from "react";
​
const Counter = () => {
  const [counter, setCounter] = useState(0);
  const counterRef = useRef(counter);
​
  const onAlertButtonClick = () => {
    setTimeout(() => {
      alert("Value: " + counterRef.current);
    }, 3000);
  };
​
  useEffect(() => {
    counterRef.current = counter;
  });
​
  return (
    <div>
      <p>You clicked {counter} times.</p>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      <button onClick={onAlertButtonClick}>
        Show me the value in 3 seconds
      </button>
    </div>
  );
};
​
export default Counter;

这时alert的是当前的值1。其实解决这个hooks的问题也可以参照类的instance。用useRef返回的immutable RefObject(current属性是可变的)来保存state,然后取值方式从counter变成了: counterRef.current 。

 

使用useSelector useDispatch 替代connect (dva hooks)

yarn add dva@2.6.0-beta.19 
// or
npm install dva@2.6.0-beta.19

并且这样使用

import { useSelector, useDispatch } from 'dva';

如果不想升级dva版本的话我们需要安装

yarn add react-redux@7.1.0

并且这样使用

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

首先先看原始dva的写法
先定义一个user model

// 1.user.js ==>model
export default {
  namespace: 'user',
  state: {
    userInfo:null,
  },
  effects: {
      *fetchUser({paylaod},{call,put}){
          const res = yield(api,payload)
          yield put({
            type: 'save',
            payload: {
                userInfo:res   
            },
          });
      }
  },
  reducers:{
      save(state, { payload }) {
        return {
            ...state,
            ...payload,
        };
      },
  }
}

然后在页面中使用


import {connect} from 'dva'

const Home = props=>{
    // 获取数据
    const {user,loading,dispatch} = props
    
    // 发起请求
    useEffect(()=>{
        dispatch({
            type:'user/fetchUser',payload:{}
        })
    },[])
    
    // 渲染页面
    if(loading) return <div>loading...</div>
    return (
        <div>{user.name}<div>
    )
}

export default connect(({loading,user})=>({
    loading:loading.effects['user/fetchUser'],
    user:user.userInfo
}))(Home)

connect这个高阶组件里定义了太多东西,这种写法太恶心了。
如果太多数据从props获取的话,connect里堆了太多代码

下面我们使用useDispatch useSelector 优化上面的代码

import {useDispatch,useSelector} from 'dva'

const Home = props=>{
    
    const dispatch = useDispatch()
    
    const loadingEffect = useSelector(state =>state.loading);
    const loading = loadingEffect.effects['user/fetchUser'];
    const user = useSelector(state=>state.user.userInfo)
    
    // 发起请求
    useEffect(()=>{
        dispatch({
            type:'user/fetchUser',payload:{}
        })
    },[])
    
    // 渲染页面
    if(loading) return <div>loading...</div>
    return (
        <div>{user.name}<div>
    )
}

export default Home

 

 

参考文章:

React Hooks官方文档

Umi Hooks官方文档

useCallback 和 useMemo 及区别

使用useSelector useDispatch 替代connect

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值