React Hook要点笔记


hook 是有状态的函数.

使用规则

  • 只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook

  • 只在 React 函数中调用 Hook

不要在普通的 JavaScript 函数中调用 Hook

state hook

和class component相比,state hook采用的是替换而非合并,因此可将state逐一声明.

useReducer和useContext

对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。

context

Context的作用就是对它所包含的组件树提供全局共享数据的一种技术

useContext以Hook的方式使用React Context。

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

// 第一步:创建需要共享的context
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}
// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  // 第三步:使用共享 Context
  const theme = useContext(ThemeContext); 
  render() {
    return <Button theme={theme} />;
  }
}

子孙类组件出发reducer状态变化。就是将dispatch函数作为context的value,共享给页面的子组件。

// 定义初始化值
const initState = {
    name: '',
    pwd: '',
    isLoading: false,
    error: '',
    isLoggedIn: false,
}
// 定义state[业务]处理逻辑 reducer函数
function loginReducer(state, action) {
    switch(action.type) {
        case 'login':
            return {
                ...state,
                isLoading: true,
                error: '',
            }
        case 'success':
            return {
                ...state,
                isLoggedIn: true,
                isLoading: false,
            }
        case 'error':
            return {
                ...state,
                error: action.payload.error,
                name: '',
                pwd: '',
                isLoading: false,
            }
        default: 
            return state;
    }
}
// 定义 context函数
const LoginContext = React.createContext();
function LoginPage() {
    const [state, dispatch] = useReducer(loginReducer, initState);
    const { name, pwd, isLoading, error, isLoggedIn } = state;
    const login = (event) => {
        event.preventDefault();
        dispatch({ type: 'login' });
        login({ name, pwd })
            .then(() => {
                dispatch({ type: 'success' });
            })
            .catch((error) => {
                dispatch({
                    type: 'error'
                    payload: { error: error.message }
                });
            });
    }
    // 利用 context 共享dispatch
    return ( 
        <LoginContext.Provider value={{dispatch}}>
            <...>
            <LoginButton />
        </LoginContext.Provider>
    )
}
function LoginButton() {
    // 子组件中直接通过context拿到dispatch,出发reducer操作state
    const dispatch = useContext(LoginContext);
    const click = () => {
        if (error) {
            // 子组件可以直接 dispatch action
            dispatch({
                type: 'error'
                payload: { error: error.message }
            });
        }
    }
}

参考

effect hook

可以把 useEffect Hook看做componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

通常我们都会在第一次dom渲染完成以及后续dom重新更新时,去调用我们的副作用操作。

Effect在默认情况下,会在第一次渲染之后和每次更新之后都会执行,这也就让我们不需要再去考虑是componentDidMount还是componentDidUpdate时执行,只需要明白Effect在组件渲染后执行即可.

可以使用Effect来清除这些副作用,只需要在Effect中返回一个函数即可:

seEffect(() => {
    pollingNewStatus()
    //告诉React在每次渲染之前都先执行cleanup()
    return function cleanup() {
      unPollingNewStatus()
    };
 });

有个明显的区别在于useEffect其实是每次渲染之前都会去执行cleanup(),而componentWillUnmount只会执行一次。

在Effect中,我们可以通过增加Effect的第二个参数即可,如果没有变化,则跳过更新:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

useCallback

把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。

通过useCallback获得一个记忆后的函数。第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback就会重新返回一个新的记忆函数提供给后面进行渲染。如果依赖项没有变,那么记忆函数的引用就不会变,当函数作为props传递给子组件时,就不会引发子组件的重新渲染:

function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

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

所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

参考: useMemo与useCallback使用指南

useMemo

useCallback 的功能完全可以由useMemo所取代.

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).

唯一的区别是:useCallback不会执行第一个参数函数,而是将它返回给你,而useMemo会执行第一个函数并且将函数执行结果返回给你。

当useCallback依赖项没有改变时,返回缓存的函数,否则返回一个新的函数。

而useMemo更适合经过函数计算得到一个确定的值,比如记忆组件或者计算的值,在依赖项没变时返回缓存的值。

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

只有依赖项a/b发生改变时,才会触发相应子组件的重新渲染。

useRef 保存引用值

可以保存对dom/组件的引用,此外可以很方便地保存任何可变值,其类似于在class中使用实例属性。在组件生命周期的每次渲染时返回同一个ref对象,而不像state返回一个新的。可以用来跨越渲染周期存储数据(即多次渲染仍然是返回同一个引用对象)。

常见的用来存储定时器。

写入它会被视为“副作用”,因此咱们无法在渲染过程中更改它,需要在useEffect hook 中才能修改。

变更 .current 属性不会引发组件重新渲染。

useLayoutEffect

useEffect传入的回调是异步的。useLayoutEffect中的副作用会在DOM更新之后立马同步执行,和原来componentDidMount&componentDidUpdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。

自定义hook

通过自定义hook来封装组件要共享的业务逻辑。自定义hook就是以use开头且调用其他hook的函数。

自定义的hook强大之处在于拥有状态,当状态值改变时,它可以触发调用组件的重新渲染,而且自定义hook可以跟随着组件的生命周期,在不同的生命钩子阶段,我们可以处理一些事件。

UI组件只需要去消费hook产出的value和function。状态处理都封装在了自定义hook里。只要符合它们的接口格式约定,UI组件就可以随时随地地复用这些逻辑。

自定义hook的设计范式:

const { state, handleChange, others } = useCustomHook(config, dependency?);
  • config声明了hook所需要的数据,可能是内部useState的初始值,也可能是结构化的数据,总结起来就是这个hook的配置
  • dependency通常只有hook内使用了useEffect、useCallback这类API,需要我们声明依赖的时候需要传入。

参考:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值