使用React Hooks传递props

React中父组件向下传递属性,可能会有并不需要这些属性的中间组件,也要一层层传递

下面是一个简单的点击按钮+1的栗子(生硬),假设有两个按钮分别操作两个数

import React, { useState } from "react";

const PlusFirst = React.memo(({ plus }) => {
  return <button onClick={plus}>Plus</button>;
});

const PlusSecond = React.memo(({ plus2 }) => {
    return <button onClick={plus2}>Plus</button>;
});

// 某打酱油的
const Header = React.memo(({ plus, plus2 }) => (
  <div className="list-header">
    <span>options: </span>
    <PlusFirst plus={plus} />
    <PlusSecond plus2={plus2} />
  </div>
));

const Demo = () => {
  const [countFirst, setCountFirst] = useState(0);
  const [countSecond, setCountSecond] = useState(0);

  const plus = () => setCountFirst(countFirst + 1);
  const plus2 = () => setCountSecond(countSecond + 1);

  return (
    <div>
      <Header plus={plus} plus2={plus2} />
      <div>count1: {countFirst}</div>
      <div>count2: {countSecond}</div>
    </div>
  )
}

export default Demo;

其中header组件并不需要操作数据的方法,如果组件嵌套的更多层,可能需要写更多的plus属性直到传递给butotn

一种解决方式是使用useContext

import React, { useState, createContext, useContext } from "react";

const fns = new Set();

const Context = createContext(null);

const PlusFirst = React.memo(() => {
  console.log('+')
  const api = useContext(Context);
  return <button className="ui button primary mr-10" onClick={api.plus}>Plus</button>;
});

const PlusSecond = React.memo(() => {
  console.log('++')
  const api = useContext(Context);
  return <button className="ui button pink" onClick={api.plus2}>Plus</button>;
});

const Header = ...

const Demo = () => {
  const [countFirst, setCountFirst] = useState(0);
  const [countSecond, setCountSecond] = useState(0);

  const plus = () => setCountFirst(countFirst + 1);
  const plus2 = () => setCountSecond(countSecond + 1)

  fns.add(plus);
  fns.add(plus2);

  return (
    <div>
      <Context.Provider value={{plus, plus2}}>
        <Header/>
        <div>functions: {fns.size - 2}</div>
        <div>count1: {countFirst}</div>
        <div>count2: {countSecond}</div>
      </Context.Provider>
    </div>
  )
}

export default Demo;

到目前为止有一个问题,每次点击按钮都会引起re-render,而且尽管两个按钮互相没有联系,点击其中一个也会影响到另一个,因为每次的plus函数都会重新创建

使用useCallbackuseMemo在render不重新创建函数,而使用memoized版本

const plus = useCallback(() => setCountFirst(prevCount => prevCount + 1), []);
const plus2 = useCallback(() => setCountSecond(prevCount => prevCount + 1), []);
const getApi = useMemo(() => ({ plus, plus2 }), [plus, plus2]); 

useCallback 和 useMemo 的区别是,前者返回memoized的函数,后者返回的是memoized的值,useCallback(fn, deps)等同于useMemo(() => fn, deps)

plus的两个函数实际上也只创建一次,它使用了prevState从而避免了依赖state.count(后者会在state.count改变时创建新的函数),useMemo那里也可以写成

const getApi = useMemo(() => ({ plus, plus2 }), []);  // eslint-disable-line

效果看着不错

另外还可以使用useReducer

import React, { useReducer, createContext, useContext } from react;

const fns = new Set();

const Context = createContext(null);

const PlusFirst = React.memo(() => {
  const dispatch = useContext(Context);
  console.log(plus button)
  return <button className=ui button primary mr-10 onClick={() => dispatch({type: plus})}>Plus</button>;
})

const PlusSecond = React.memo(() => {
  const dispatch = useContext(Context);
  console.log(plus2 button)
  return <button className=ui button pink onClick={() => dispatch({type: plus2})}>Plus</button>;
})

const Header = () => (
  <div className=list-header>
    <span>options: </span>
    <PlusFirst />
    <PlusSecond />
  </div>
)

const Demo = () => {
  const initState = { countFirst: 0, countSecond: 0 };
  const reducer = (state, action) => {
    switch (action.type) {
      case plus:
        return { state, countFirst: state.countFirst + 1};
      case plus2:
        return { state, countSecond: state.countSecond + 1};
      default:
        return state;
    }
  }

  const [state, dispatch] = useReducer(reducer, initState);

  return (
    <div>
      <Context.Provider value={dispatch}>
        <Header />
        <div>functions: {fns.size}</div>
        <div>result1: {state.countFirst}</div>
        <div>result2: {state.countSecond}</div>
      </Context.Provider>
    </div>
  )
}

export default Demo;

参考:
Passing callbacks down with ReactHooks
React’s useCallback and useMemo Hooks By Example
Hooks API Reference – React

Comments
Write a Comment