react hooks总结

函数组件与类组件区别

  1. 类组件继承自React.component,使用需要实例化new
    App(props),React.component中有生命周期函数 函数组件直接调用App(props)
  2. this
    类组件this随着渲染一直变更,以确保this是最新的,this.props也是最新值,如果某个行为和this.props有关联,这个行为执行完成之前组件重新渲染,将导致我们读取到改变之后的值。
    改进:使用闭包保存渲染之前的props
    函数组件若想使用最新props,在useEffect中使用即可

使用hooks的优势

  1. 在组件之间复用逻辑 自定义hooks & 避免地狱式嵌套,hoc代码复用需要层层包裹,嵌套层级深
  2. 组件之内的逻辑维护 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中
    函数组件相同的逻辑放在一个useEffect或者自定义hook之中
  3. hooks和现有代码可以同时工作
  4. 代码简洁,不需要记忆生命周期函数,维护状态方便等

一些hook的简介

useState

传入初始值,返回一个state以及更新state的函数。若初始值为函数,函数只在初始渲染时被调用
模拟实现useState(利用数组存储state和setter,闭包存储指针)

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
  if (firstRun) {
    state.push(initVal);
    setters.push(createSetter(cursor));
    firstRun = false;
  }
  const setter = setters[cursor];
  const value = state[cursor];
  cursor++;
  return [value, setter];
}

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
})

useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。

useEffect

函数组件渲染到屏幕之后执行,可以只有某些值改变之后执行
(React只会在浏览器绘制后运行effects。这使得你的应用更流畅因为大多数effects并不会阻塞屏幕的更新。Effect的清除同样被延迟了。上一次的effect会在重新渲染后被清除)

useEffect(() => {
  //每次render后执行
  return () => {
  //当前 effect 之前对上一个 effect 进行清除
  }
})
useEffect(() => {
  //仅在第一次render后执行
  return () => {
  //组件卸载前执行
  }
}, [])
useEffect(() => {
  //arr变化后,render后执行
  return () => {
  //下一useEffect运行前执行
  }
}, arr)
清除副作用
 const [value, setValue] = useState()
useEffect(() => {
    const timer1 = setTimeout(() => {
      console.log('send')
      console.log(value)
    }, 2000)
    return () => {
      console.log('clearLastSend')
      clearTimeout(timer1)
    }
  }, [value])
<Input value={value} placeholder="input here" onInput={e => setValue(e.target.value)} />
fetch data
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
  const [data, setData] = useState({ hits: [] })
  const [query, setQuery] = useState(initialQuery)
  useEffect( () => {
  	//不要直接使用async函数,useEffect期待返回清除函数而不是promise
    //const result = await axios(
      //'http://hn.algolia.com/api/v1/search?query=redux',
    //);
    //setData(result.data);
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`
      )
      setData(result.data);
    }
    fetchData();
  }, [query]);
  return (
   ...
}
export default App;

上面的代码注释处有一个问题。我们使用了 async/await 来处理异步操作,根据规范,async 函数会返回一个隐式的 Promise。然而 effect hook 要么什么都不返回,要么返回一个清理函数。因此,运行注释的代码,你会在 console 里看到这样的警告:Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect. 因此,不能直接给 useEffect 传一个 async 函数,我们需要在 useEffect 内部,定义一个单独的 async 函数。

useEffect更新机制

每个hook存放在组件对应的fiberNode对应的memoizedState中,以链表的形式存储,第一个hook的next指向下一个hook,以此类推。每个hook还有自己的memoizedState,useState的memoizedState存储的就是最新值,useEffect的memoizedState是个单向循环链表,存储的是一个个(下次渲染会使用到的)effect对象,有属性tag,用于区分是否要执行改useEffect。useEffect的执行分mount和update两种,因为mount阶段的useEffect都会执行,所以mount阶段的effect都会被打上HookHasEffect的tag标记。之后每次创建新的effect的时候都会比较当前deps和前一次是否发生改变,如果改变,打上HookHasEffect的tag标记,若没变,打上HookTagEffect的tag标记。
react hooks总结_第1张图片

tag就是一个二进制数,用来标记effect,从而决定它的行为。以下是 ReactSideEffectTags.js 与 ReactHookEffectTags.js 中的定义。
export const NoEffect = /* / 0b00000000;
export const UnmountSnapshot = /
/ 0b00000010;
export const UnmountMutation = /
/ 0b00000100;
export const MountMutation = /
/ 0b00001000;
export const UnmountLayout = /
/ 0b00010000;
export const MountLayout = /
/ 0b00100000;
export const MountPassive = /
/ 0b01000000;
export const UnmountPassive = /
*/ 0b10000000;

useContext

使得函数组件也能使用context
Context 全局数据,不需逐层传递

const ct = createContext(defaultValue)
const GrandFather = () => {
return <ct.Provider value={'grandsonName'}><Father /></ct.Provider>
}
const Father = () => {
return <Son />
}
const Son = () => {
return <Grandson />
}
const Grandson = () => {
const name = useContext(ct)
return <div>{name}</div>
}
useCallback&useMemo

传入一个回调函数和依赖项(数组),依赖项变化时回调
useCallback(callback,depArray)
&useMemo(()=>{callback},depArray)
useCallback返回该回调函数的 memoized 版本,在依赖参数不变的情况下,返回的回调函数是同一个引用地址,仅在某个依赖项改变时回调函数才会更新(地址变化)。用于避免自组件的非必要性渲染
useMemo返回一个 memoized 值,在依赖参数不变的的情况返回的是上次第一次计算的值。优化针对于当前组件高开销的计算

useRef

1 获取DOM元素的节点
2 获取子组件的实例
3 渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)
createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用
如果想要返回最新的引用,在useEffect中赋值即可

import React, { useState, useEffect, useRef } from "react";
function App() {
  const [count, setCount] = useState(0);
  // 把定时器设置成全局变量使用useRef挂载到current上
  const timer = useRef();
  // 首次加载useEffect方法执行一次设置定时器
  useEffect(() => {
    timer.current = setInterval(() => {
      setCount(count => count + 1);
    }, 1000);
  }, []);
  // count每次更新都会执行这个副作用,当count > 5时,清除定时器
  useEffect(() => {
    if (count > 5) {
      clearInterval(timer.current);
    }
  });
  return <h1>count: {count}</h1>;
}
export default App
 
useReducer
const [state, dispatch] = useReducer(reducer, initialState)
const [state, dispatch] = useReducer(reducer, initialArg, init)

传入reducer&初始值,返回新值&dispatch
把state的更新逻辑迁移到effect之外,在reducer中对状态进行管理,根据diapatch的不同行为返回新的state。
这么做可以将用于计算 state 的逻辑提取到 reducer 外部
使用useReducer的场景
• 如果你的state是一个数组或者对象
• 如果你的state变化很复杂,经常一个操作需要修改很多state
• 如果你想将数据处理和展示分隔开

自定义hooks

共享逻辑的方案:render props 使用值为函数的prop 来共享代码/高阶组件/自定义hooks
两个函数的共享逻辑,将其提取到第三个函数中。
两个函数组件的共享逻辑,提取到一个自定义hook中,名称以use开头,hook内部顶层调用其他hook

function useInterval(callback, delay) {
    const lastestCallback = useRef(() => {})
    useEffect(() => {
      lastestCallback.current = callback
    })
    useEffect(() => {
      if (delay !== null) {
        const interval = setInterval(() => lastestCallback.current(), delay || 0)
        return () => clearInterval(interval)
      }
      return undefined
    }, [delay])
  }
useInterval(() => setAge(age - 1), 1000)
useInterval(() => setName(name + 'y'), 2000)
使用规则

1 use开头…
2 顶层使用(不在判断、循环、嵌套函数中使用):react靠hooks的调用顺序,来表示不同的hooks
hooks存储在fiber树中组件对应的fiberNode的memoizedState中,以链表的形式有序存储,之后被react机械地读取。

hook周边库

mobx hooks
mobx相关hooks:https://mobx-react.js.org/observer-hook

import { useObserver, useLocalStore } from 'mobx-react' // 6.x or [email protected]
function Person() {
  const person = useLocalStore(() => ({ name: 'John' }))
  return useObserver(() => (
    <div>
      {person.name}
      <button onClick={() => (person.name = 'Mike')}>No! I am Mike</button>
    </div>
  ))
}

useObserver
返回DOM的地方使用useOberver,一旦其中observable数据变动,整个组件重新render
useLocalStore
执行传入的函数,创建组件的store(for a lifetime of a component),将对象变为observable ,getter变为computed,方法变为actions
useRoutes:https://blog.logrocket.com/how-react-hooks-can-replace-react-router/
mobx-react-lite : https://github.com/mobxjs/mobx-react-lite
collections of hooks :https://nikgraf.github.io/react-hooks/
storybook : https://beizhedenglong.github.io/react-hooks-lib/

你可能感兴趣的:(react,hooks,js)