TypeScript and React: Hooks

React Hooks

Hooks以前将“无状态”功能组件放到了……基本上,传统类组件都可以做到。使用更干净的API!在React 16.7中发布后不久,DefinitelyTyped中的React类型也得到了更新。了解如何在TypeScript中使用钩子!

useState

import React, { FunctionComponent, useState } from 'react';

const Counter:FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
  const [clicks, setClicks] = useState(initial);
  return <>
    <p>Clicks: {clicks}</p>
    <button onClick={() => setClicks(clicks+1)}>+</button>
    <button onClick={() => setClicks(clicks-1)}>-</button>
  </>
}

useState 的使用方式

const [keys, setKeys] = useState<{ currentOpenSubs: string[], currentSideMenu: string }>({
    currentSideMenu: '',
    currentOpenSubs: [],
});

useEffect

useEffect在这里有所有副作用。添加事件侦听器,更改文档中的内容,获取数据。一切你所使用的(组件生命周期方法componentDidUpdatecomponentDidMountcomponentWillUnmount)的方法签名是非常简单的。它接受两个参数:

  • 无需任何参数即可调用的函数。这是您要调用的副作用。
  • 类型的值数组any。此参数是可选的。如果不提供,则每次组件更新时都会调用提供的功能。如果这样做,React将检查这些值是否确实发生了变化,并且仅在存在差异时才触发函数。
useEffect(() => {
  const handler = () => {
    document.title = window.width;
  }
  window.addEventListener('resize', handler);

  return true;
  return () => {
    window.removeEventListener('resize', handler);
  }
})

useContext

useContext允许您从组件中的任何位置访问上下文属性。非常类似于Context.Consumer 类组件中的do。类型推断在这里非常出色,您不需要使用任何TypeScript特定的语言功能就可以完成所有工作:

import React, { useContext } from 'react';

export const LanguageContext = React.createContext({ lang: 'en' });

const Display = () => {
  const { lang } = useContext(LanguageContext);
  return <>
    <p>Your selected language: {lang}</p>
  </>
}

useRef

function TextInputWithFocusButton() {
  
  const inputEl = useRef<HTMLInputElement>(null);
  const onButtonClick = () => {
   
    if(inputEl && inputEl.current) {
      inputEl.current.focus();
    } 
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useMemo-useCallback

useMemo返回一个 memoized 值。 传递“创建”函数和依赖项数组。useMemo 只会在其中一个依赖项发生更改时重新计算 memoized 值。此优化有助于避免在每个渲染上进行昂贵的计算。

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

useMemo 在渲染过程中传递的函数会运行。不要做那些在渲染时通常不会做的事情。例如,副作用属于 useEffect,而不是 useMemo。

您知道useEffect,可以通过向其传递一些参数来影响某些函数的执行。React检查这些参数是否已更改,并且仅在存在差异的情况下才会执行此功能。
useMemo做类似的事情。假设您有大量计算方法,并且只想在其参数更改时运行它们,而不是每次组件更新时都运行它们。useMemo返回记忆的结果,并且仅在参数更改时才执行回调函数。

function getHistogram(image: ImageData): number[] {
  ...
  return histogram;
}

function Histogram() {
  ...
  const histogram = useMemo(() => getHistogram(imageData), [imageData]);
}

useCallback非常相似。实际上,这也是可以表达的捷径useMemo。但是它返回一个回调函数,而不是一个值。

const memoCallback = useCallback((a: number) => {
  // doSomething
}, [a])

useMemo和useCallback的作用有点像啊,那它们之间有什么区别呢?

  • useCallback 和 useMemo 都可缓存函数的引用或值。
  • 从更细的使用角度来说 useCallback 缓存函数的引用,useMemo 缓存计算数据的值。

useReducer

如果您以前使用过Redux,则应该很熟悉。useReducer接受 3 个参数(reducer,initialState,init)并返回当前的 state 以及与其配套的 dispatch 方法。reducer 是如下形式的函数(state, action) => newState;initialState 是一个 JavaScript 对象;而 init 参数是一个惰性初始化函数,可以让你延迟加载初始状态。
const [state,dispatch] = useReducer(reducer,initialState,init);

type ActionType = {
  type: 'reset' | 'decrement' | 'increment'
}

const initialState = { count: 0 };

// We only need to set the type here ...
function reducer(state, action: ActionType) {
  switch (action.type) {
    // ... to make sure that we don't have any other strings here ...
    case 'reset':
      return initialState;
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter({ initialCount = 0 }) {
  const [state, dispatch] = useReducer(reducer, { count: initialCount });
  return (
    <>
      Count: {state.count}
      { /* and can dispatch certain events here */ }
      <button onClick={() => dispatch({ type: 'reset' })}>
        Reset
      </button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

React-redux hooks

React Redux 现在提供了一系列 hook APIs 作为现在 connect() 高阶组件的替代品。这些 APIs 允许你,在不使用 connect() 包裹组件的情况下,订阅 Redux 的 store,和 分发(dispatch) actions。

useSelector()

const result : any = useSelector(selector : Function, equalityFn? : Function)

基本用法

import React from 'react'
import { useSelector } from 'react-redux'

export const CounterComponent = () => {
  const counter = useSelector(state => state.counter)
  return <div>{counter}</div>
}

通过闭包使用 props 来选择取回什么状态:

import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = props => {
  const todo = useSelector(state => state.todos[props.id])
  return <div>{todo.text}</div>
}

**使用记忆化的 selectors 函数**
当像上方展示的那样,在使用 useSelector 时使用单行箭头函数,会导致在每次渲染期间都会创建一个新的 selector 函数。可以看出,这样的 selector 函数并没有维持任何的内部状态。但是,记忆化的 selectors 函数 (通过 reselect 库中 的 createSelector 创建) 含有内部状态,所以在使用它们时必须小心。

当一个 selector 函数依赖于某个 状态(state) 时,确保函数声明在组件之外,这样就不会导致相同的 selector 函数在每一次渲染时都被重复创建:

import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumOfDoneTodos = createSelector(
  state => state.todos,
  todos => todos.filter(todo => todo.isDone).length
)

export const DoneTodosCounter = () => {
  const NumOfDoneTodos = useSelector(selectNumOfDoneTodos)
  return <div>{NumOfDoneTodos}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <DoneTodosCounter />
    </>
  )
}

被移除的:useActions()

import { useMemo, DependencyList } from 'react';
import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
// 这个主要是用于配合saga  处理异步请求
import {
  bindPromiseCreators,
  PromiseCreator,
  ActionCreatorFunction,
  Routine,
} from 'redux-saga-routines';

type actionType = Routine | PromiseCreator | ActionCreatorFunction;

// hooks 一定要以use开头
function useActions(
  actions: {
    [kye: string]: actionType;
  },
  deps?: DependencyList | undefined
): any {

  const dispatch = useDispatch();

  return useMemo(() => {
    const newActions = actions;
    const keys = Object.keys(actions);
    keys.forEach((key: string) => {
      if( newActions[key].length === 2 ) {
        newActions[key] = bindPromiseCreators((actions[key] as PromiseCreator), dispatch);
      } else {
        newActions[key] = bindActionCreators((actions[key] as Routine), dispatch);
      }
    })

    return newActions;
  }, deps ? [dispatch, ...deps] : [dispatch]);

}

useAction的使用方式

 const { topMenu, currentSidebar, theme, drawer, primaryColor }  = useSelector((state: IState) => state.menu);

 const [collapsed, setCollapsed] = useState(false);

 const actions = useActions({
    setMenu: menuAction.setMenu,
    setDrawer: menuAction.setDrawer,
    setTheme: menuAction.setTheme,
 });

const handleSettingClick = useCallback((values) => {
    actions.setTheme(values);
  }, [actions]);

useDispatch() 这个 hook 返回 Redux store 的 分发(dispatch) 函数的引用。你也许会使用来 分发(dispatch) 某些需要的 action。

import React from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()

  return (
    <div>
      <span>{value}</span>
      <button onClick={() => dispatch({ type: 'increment-counter' })}>
        Increment counter
      </button>
    </div>
  )
}

在将一个使用了 dispatch 函数的回调函数传递给子组件时,建议使用 useCallback 函数将回调函数记忆化,防止因为回调函数引用的变化导致不必要的渲染。

这里的建议其实和 dispatch 没关系,无论是否使用 dispatch,你都应该确保回调函数不会无故变化,然后导致不必要的重渲染。之所以和 dispatch 没关系,是因为,一旦 dispatch 变化,useCallback 会重新创建回调函数,回调函数的引用铁定发生了变化,然而导致不必要的重渲染。

import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()
  const incrementCounter = useCallback(
    () => dispatch({ type: 'increment-counter' }),
    [dispatch]
  )

  return (
    <div>
      <span>{value}</span>
      <MyIncrementButton onIncrement={incrementCounter} />
    </div>
  )
}

export const MyIncrementButton = React.memo(({ onIncrement }) => (
  <button onClick={onIncrement}>Increment counter</button>
))

useStore() 这个 hook 返回传递给 组件的 Redux sotore 的引用。

这个 hook 也许不应该被经常使用。 你应该将 useSelector() 作为你的首选。但是,在一些不常见的场景下,你需要访问 store,这个还是有用的,比如替换 store 的 reducers。

import React from 'react'
import { useStore } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const store = useStore()
  return <div>{store.getState()}</div>
}
原文地址:https://www.cnblogs.com/boyGdm/p/14101557.html