接着上篇讲 react hook

react hook

这里主要讲 hook 的语法和使用场景css

hook

Hook 是一个特殊的函数,使用了 JavaScript 的闭包机制,可让你在函数组件里“钩入” React state 及生命周期等特性。Hook 不能在 class 组件中使用。这也就是我开篇说的函数式组件一把索的缘由html

Hook 的调用顺序在每次渲染中都是相同的,因此它可以正常工做,只要 Hook 的调用顺序在屡次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但若是咱们将一个 Hook 调用放到一个条件语句中会发生什么呢?vue

答案:Hook 的调用顺序发生了改变出现 bug Hook 规则react

userState

是容许你在 React 函数组件中数据变化能够异步响应式更新页面 UI 状态的 hook。git

userState 函数初始化变量值,返回一个数组,数组第一项是这个初始化的变量,第二项是响应式修改这个变量的方法名。github

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。能够声明不少个
  const [count, setCount] = useState<number>(0); // 数组解构,在typescript中使用,咱们能够用以下的方式声明状态的类型
  const [fruit, setFruit] = useState<string>('banana');

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

userState 的返回的第二个参数能够接受一个函数,若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。注意了 useState 不会自动合并更新对象,因此运算符来达到合并更新对象的效果。vuex

function Box() {
  const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
    useEffect(() => {
    function handleWindowMouseMove(e) {
      // 展开 「...state」 以确保咱们没有 「丢失」 width 和 height
      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
    }
  });// 没有第二个参数,只会渲染一次,永远不会重复执行
}
复制代码

通常状况下,咱们使用 userState hook,给他传的是一个简单值,可是若是初始 state 须要经过复杂计算得到,则能够传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用typescript

const [state, setState] = useState(() => {
  return  doSomething(props);
});
复制代码

useState 返回的更新状态方法是异步的,下一个事件循环周期执行时,状态才是最新的值。不要试图在更改状态以后立马获取状态。这里有可能会出现陈旧值引用的问题,这并非 reatc 的 bug,是由于 JavaScript 的正常表现,是由于闭包api

函数式组件与类组件在线区别 demo数组

好比使用 immutable.js 里面的 set 结构的时候,进行循环删除里面的某些项,结果删除的永远是数组的最后一项

infos.forEach((el) => {
  if( list.has(el.id)){
    setList(list.delete(item.id))// 这里是异步,在你循环的时候,页面尚未重绘,拿不到最后一个值
    }
  })
复制代码

若是咱们想要实现循环里面删除,那么怎么作呢?别忘了,useState 是想要咱们直接修改 DOM 的渲染,因此才使用他的。咱们能够先总体的修改完以后再去影响 DOM 的渲染

infos.forEach((el) => {
    if (list.has(el.id)) {
      list = list.delete(el.id)//这里是同步删除
    }
  })
  setList(list)//删除完了以后,在去修改DOM的结构
复制代码

React 这样设计的目的是为了性能考虑,争取把全部状态改变后只重绘一次就能解决更新问题,而不是改一次重绘一次,也是很容易理解的.内部是经过 merge 操做将新状态和老状态合并后,从新返回一个新的状态对象,组件中出现  setTimeout  等闭包时,尽可能在闭包内部引用 ref 而不是 state,不然容易出现读取到旧值的状况.闭包引用的是原来的旧值,一旦通过 setUsetate,引用的就是一个新的对象,和原来的对象引用的地址不同了。

可以直接影响 DOM 的变量,这样咱们才会将其称之为状态。当某一个变量对于 DOM 而言没有影响,此时将他定义为一个异步变量并不明智。好的方式是将其定义为一个同步变量。

React Hooks 异步操做踩坑记

useReducer

useState 的替代方案,升级版,但咱们遇到多个 useState 之间互相影响,须要或者说只是某一个参数不同,其余的大体差很少的时候,咱们就可使用 useReducer 替换,这个有点像 vue 里面的 vuex 的感受,也有点 Redux 的感受,可是只是有一点点,几个仍是彻底不同的概念

const initialState = {count: 0};

// 多个 useState 结合成一个了
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, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

复制代码

useReducer

The State Reducer Pattern with React Hooks

React Hooks 的体系设计之一 - 分层

Umi Hooks - 助力拥抱 React Hooks

Effect Hook

React 会等待浏览器完成画面渲染以后才会延迟调用 useEffect,他至关于 react class 的三个生命周期函数 componentDidMount(组件挂载完成),componentDidUpdate(组件更新) 和 componentWillUnmount(组件将要销毁) 三个生命周期函数的组合,能够实现减小重复代码的编写

componentDidMount: 组件挂载完成的时候,须要执行一堆东西

componentDidUpdate:组件更新钩子函数,就理解成 vue 里面的 watch 吧,当你监听的某一个数据发生变化的时候,就会执行这一个 Effect Hook 钩子函数里面的东西。

componentWillUnmount:清除 effect ,在某种状况下,你须要清理一些数据为了不内存泄露的时候就能够用它。 返回一个函数,就表示你要作的清空操做了。不返回一个函数就表示不须要作清空操做。(组件卸载,

const [debounceVal, setDebounceVal] = useState(value)
  useEffect(() => {
    const handle = setTimeout(() => {
      setDebounceVal(value)
    }, delay)
    return () => {
      clearTimeout(handle) // 组件销毁的时候清空定时器
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value])
  return debounceVal
复制代码

默认状况下,它在第一次渲染以后和每次更新以后都会执行,并且 effect 的清除阶段在每次从新渲染时都会执行,这个能就会致使性能问题 ,因此他又称是反作用。他能够接受第二个参数,他会对比更新先后的两个数据,若是没有变化的话,就不执行 hook 里面的东西。仅仅只有在第二次参数发生变化的时候才会执行。这样就避免没有必要的重复渲染和清除操做

能够传递一个空数组([])做为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,因此它永远都不须要重复执行。意味着该 hook 只在组件挂载时运行一次,并不是从新渲染时,(须要注意的是[]是一个引用类型的值,在某些状况下自定义 hooks,他做为第二个参数也会致使页面从新渲染,由于引用地址变了,因此在自定义 hooks 的时候须要注意,在自定义 hook 详细说

useEffect 完整指南 -> 这个写的特别好,特别推荐看学习

超性感的 React Hooks(四):useEffect

useMemo

简单说就是把一些须要计算可是不会变得数据存储在本地,下次用的时候直接拿计算的结果就行了,不须要计算( 若是咱们有 CPU 密集型操做,咱们能够经过将初始操做的结果存储在缓存中来优化使用。若是操做必然会再次执行,咱们将再也不麻烦再次使用咱们的 CPU,由于相同结果的结果存储在某个地方,咱们只是简单地返回结果他经过内存来提高速度,React.useMemo 是新出来的 hooks api,而且这个 api 是做用于 function 组件,此方法仅做为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,由于这会产生 bug。

把“建立”函数和依赖项数组做为参数传入 useMemo,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

function App() {
  const [num, setNum] = useState(0);

  // 一个很是耗时的一个计算函数
  // result 最后返回的值是 49995000
  function expensiveFn() {
    let result = 0;

    for (let i = 0; i < 10000; i++) {
      result += i;
    }

    console.log(result) // 49995000
    return result;
  }

  const base = expensiveFn();
  //  const base = useMemo(expensiveFn, []); 只有在第一次点击的时候才会执行,后来都不执行了,他的第二个参数和useEffect同样的意思

  return (
    <div className="App">
      <h1>count:{num}</h1>
      <button onClick={() => setNum(num + base)}>+1</button>
    </div>
  );
}
复制代码

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操做,诸如反作用这类的操做属于 useEffect 的适用范畴,而不是 useMemo

useCallback

父组件给子组件传递函数的时候,父组件每一次的修改都会从新渲染,都会致使它们在每次渲染上都有不一样的引用,最后的结果是,每一次父组件的修改都直接致使了子组件没有必要的渲染。(引用类型

这个时候咱们吧把函数以及依赖项做为参数传入 useCallback,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新。

给定相同 props 的状况下渲染相同的结果,而且经过记忆组件渲染结果的方式来提升组件的性能表现,第二个参数表明的意义和上面的同样

// 避免引用类型的重复渲染

const handleIndicator = useCallback((indicator: EvaluateIndicator) => {
    console.log('传给字组件')
  }, [])
复制代码

// 函数防抖

import React, { useState, useCallback } from 'react'
import { debounce } from '../../utils/tool'

import './index.scss'
interface searchlParams {
  handleSearch: (val: string) => void
}
const Search: React.FC<searchlParams> = ({ handleSearch }) => {
  const [value, setValue] = useState<string>('')
  /* 防抖的另一种写法 */
  const debounceSearch = useCallback(
    debounce((val) => handleSearch(val), 2000),
    [],
  )
  const changhandleSearch = (e: any) => {
    setValue(e.target.value)
    debounceSearch(e.target.value)
  }
  return (
    <div className="search-wrapper">
      <input className="input-control" value={value} onChange={changhandleSearch} placeholder="搜索" />
    </div>
  )
}
复制代码

子组件须要配合 React.memo 的使用,React.memo 和 useCallback 都是为了减小从新 render 的次数

useCallback 和 useMemo 均可缓存函数的引用或值,可是从更细的使用角度来讲 useCallback 缓存函数的引用,useMemo 缓存计算数据的值

如何对 React 函数式组件进行优化

浅谈 React 性能优化的方向

useCallback、useMemo 分析 & 差异

React.memo

能够减小从新 render 的次数的。

//子组件

function Child(props) {
  console.log(props.name)
  return <h1>{props.name}</h1>
}

export default React.memo(Child)

// 父组件
function App() {
  const [count, setCount] = useState<number>(1)

  return (
    <div className="App">
      <h1>{ count }</h1>
      <button onClick={() => setCount(count+1)}>改变数字</button>
      <Child name="sunseekers"></Child>
    </div>
  );
}
复制代码

若是你的函数组件在给定相同 props 的状况下渲染相同的结果,那么你能够经过将其包装在 React.memo 中调用,以此经过记忆组件渲染结果的方式来提升组件的性能表现。这意味着在这种状况下,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。(若是没有用 React.memo 包裹,每一次 count 变化,子组件都会从新渲染)

仅检查 props 变动。若是函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会从新渲染.默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现

如何对 React 函数式组件进行优化

useRef

至关于 vue 里面的 refs ,只是在这边的用法不同而已。useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变,当咱们遇到了由于闭包问题致使的陈旧值引用的问题,咱们就能够用它来解决问题

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>
    </>
  );
}
复制代码

在更新过程当中它会被执行两次,第一次传入参数 null,而后第二次会传入参数 DOM 元素,因此在控制太能够打印两条数据信息出来

Refs and the DOM

refs 经过函数引用 demo

The State Reducer Pattern with React Hooks

自定义 Hook

这个有就有点像 vue 里面的 mixin 了,当咱们在多个组件函数里面共同使用同一段代码,而且这段代码里面包含了 react 的 hook,咱们想在多个组件函数共享逻辑的时候,咱们能够把他提取到第三个函数中去,而组件和 Hook 都是函数,因此也一样适用这种方式。

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部能够调用其余的 Hook,在两个组件中使用相同的 Hook 不会共享 state,是独立的 state

  1. 接口请求,在每个接口前面都加一个 loading
import { useState, useCallback, useEffect } from 'react'

export function useFriendStatus(fn, dependencies) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)

  // 请求的方法 这个方法会自动管理loading
  const request = useCallback(() => {
    setLoading(true)
    setData(fn)
    setLoading(false)
  })
  // 根据传入的依赖项来执行请求
  useEffect(() => {
    request()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dependencies])

  return {
    // 请求获取的数据
    data,
    // loading状态
    loading,
    // 请求的方法封装
    request,
  }
}

// 组件中使用
const { data, loading } = useFriendStatus(fetchTodos({ tab: 'activeTab' }), 'activeTab')

复制代码

若是 dependencies 是引用类型的要注意了,会致使每一次加载页面引用的地址都不同,直接致使页面死循环,因此处理的时候, 要特别当心和注意了。好比说,若是咱们给 useFriendStatus 第二个参数一个空数组,每一次请求接口页面就会从新渲染,第二个参数的空数组引用地址变了,会致使死循环,本身尝试

  1. 函数防抖
//@ts-ignore
import React, { useState, useEffect } from 'react'
export default function useDebounce(value, delay) {
  const [debounceVal, setDebounceVal] = useState(value)
  useEffect(() => {
    const handle = setTimeout(() => {
      setDebounceVal(value)
    }, delay)
    return () => {
      clearTimeout(handle)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [value])
  return debounceVal
}
// 组件中使用
interface searchlParams {
  handleSearch: (val: string) => void
}
const Search = ({ handleSearch:searchlParams }) => {
  const [value, setValue] = useState<string>('')
  // 函数防抖,每一次内部变量变化都会注册和执行setTimeout,函数从新渲染以后
  const debounceSearch = useDebounce(value, 2000)
  useEffect(() => {
    handleSearch(value)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debounceSearch])

  return (
    <div className="search-wrapper">
      <input className="input-control" value={value} onChange={(e) => setValue(e.target.value)} placeholder="搜索" />
      {/* <i className="iconfont ico search-ico">&#xe6aa;</i> */}
    </div>
  )
}
复制代码

在 react 函数式组件中使用防抖与节流函数

自定义 Hook

使用 React Hooks + 自定义 Hook 封装一步一步打造一个完善的小型应用

原文连接,会保持一直更新,建议关注原文,最新更新

相关文章
相关标签/搜索