由于上下文中未使用的属性,组件重新渲染

Posted

技术标签:

【中文标题】由于上下文中未使用的属性,组件重新渲染【英文标题】:Component re-renders because of unused property in context 【发布时间】:2019-10-29 08:50:55 【问题描述】:

我有一个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 的孩子。

第二次尝试是直接在JSX上使用useMemo

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】:

我想我找到了最好的方法,这要感谢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) 并且我没有重新渲染。

【讨论】:

以上是关于由于上下文中未使用的属性,组件重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

React Context API 似乎要重新渲染每个组件

React Context API似乎重新渲染每个组件

上下文提供程序中的 componentDidMount() 状态更改未触发子组件的重新渲染

有没有办法让上下文仅在其数据更改时重新渲染?

为啥子组件中未定义上下文?

上下文提供程序未触发 this.props.children 的重新渲染