Компонент перерисовывается из-за неиспользуемого свойства в контексте - PullRequest
2 голосов
/ 14 июня 2019

У меня есть AsyncContext, который позволяет мне запускать / останавливать любые асинхронные вычисления.За кулисами он управляет глобальным загрузчиком и снэк-баром.

export type Context = {
  loading: boolean
  start: () => void
  stop: (message?: string) => void
}

const defaultContext: Context = {
  loading: false,
  start: noop,
  stop: noop,
}

export const AsyncContext = createContext(defaultContext)

Здесь потребитель:

const MyChild: FC = () => {
  const {start, stop} = useContext(AsyncContext)

  async function fetchUser() {
    try {
      start()
      const res = await axios.get('/user')
      console.log(res.data)
      stop()
    } catch (e) {
      stop('Error: ' + e.message)
    }
  }

  return (
    <button onClick={fetchData}>
      Fetch data
    </button>
  )
}

Как видите, MyChild не заботится о loading,Но он включен в контекст, поэтому компонент повторно выполняет рендеринг 2 раза.

Чтобы предотвратить это, моя первая попытка состоит в том, чтобы разделить мой компонент на две части и использовать memo:

type Props = {
  start: AsyncContext['start']
  stop: AsyncContext['stop']
}

const MyChild: FC = () => {
  const {start, stop} = useContext(AsyncContext)
  return <MyChildMemo start={start} stop={stop} />
}

const MyChildMemo: FC<Props> = memo(props => {
  const {start, stop} = props

  async function fetchUser() {
    try {
      start()
      const res = await axios.get('/user')
      console.log(res.data)
      stop()
    } catch (e) {
      stop('Error: ' + e.message)
    }
  }

  return (
    <button onClick={fetchData}>
      Fetch data
    </button>
  )
})

Это работает, но я не хочу разделять всех потомков, которые используют AsyncContext.

Вторая попытка - использовать useMemo непосредственно на JSX:

const MyChild: FC = () => {
  const {start, stop} = useContext(AsyncContext)

  async function fetchUser() {
    try {
      start()
      const res = await axios.get('/user')
      console.log(res.data)
      stop()
    } catch (e) {
      stop('Error: ' + e.message)
    }
  }

  return useMemo(() => (
    <button onClick={fetchData}>
      Fetch data
    </button>
  ), [])
}

Это также работает, оно более сжато, но я не уверен, что это хорошая практика.

Является ли какой-либо из моих двух подходов правильным?Если нет, что бы вы посоветовали?

1 Ответ

0 голосов
/ 15 июня 2019

Я думаю, что нашел лучший подход, благодаря https://kentcdodds.com/blog/how-to-use-react-context-effectively: для разделения контекста в двух контекстах.Один для государства, другой для отправки:

type StateContext = boolean
type DispatchContext = {
  start: () => void
  stop: (message?: string | void) => void
}

export const AsyncStateContext = createContext(false)
export const AsyncDispatchContext = createContext({start: noop, stop: noop})

Если потребителю не нужно состояние, я просто добавляю const {start, stop} = useContext(AsyncDispatchContext), и у меня нет повторных визуализаций.

...