React Context 不渲染组件

Posted

技术标签:

【中文标题】React Context 不渲染组件【英文标题】:React Context doesn't render component 【发布时间】:2020-02-29 00:14:14 【问题描述】:

我正在使用上下文和 React 路由器。当我创建上下文并包装我的组件时,没有渲染任何内容。当我检查控制台时,我可以看到我正在获取的记录数据(它来自 AnimeContext 中的 useEffect),但没有出现 Header 和 HomePage 组件。

我正在尝试在HomePage 上显示topTvtopAiringtopUpcoming

这是repo

上下文文件

import React,  useState, useEffect, createContext  from 'react'

const AnimeContext = createContext()

const API = "https://api.jikan.moe/v3"


const AnimeProvider = (props) => 
  const urls = [
    `$API/top/anime/1/airing`,
    `$API/top/anime/1/tv`,
    `$API/top/anime/1/upcoming`,
  ]

  // State for top Anime 
  const [topTv, setTopTv] = useState([])
  const [topAiring, setTopAiring] = useState([])
  const [topUpcoming, setTopUpcoming] = useState([])

  // State for Anime details
  const [animeReq, setAnimeReq] = useState(
    fetching: false,
    anime: []
  )

  // State for Anime search form
  const [dataItems, setDataItems] = useState([])
  const [animeSearched, setAnimeSearched] = useState(false)


  // Fetch top Anime 
  const fetchTopAnime = async () => 
    return Promise.all(
      urls.map(async url => 
        return await fetch(url); // fetch data from urls
      )
    )
      .then((responses) => Promise.all(responses.map(resp => resp.json())) // turn data into JSON
        .then(data => 
          const topTvFiltered = data[0].top.filter(item => item.rank <= 5) // filter out top 6 
          const topAiringFiltered = data[1].top.filter(item => item.rank <= 5)
          const topUpcomingFiltered = data[2].top.filter(item => item.rank <= 5)

          setTopTv(topTvFiltered)
          setTopAiring(topAiringFiltered)
          setTopUpcoming(topUpcomingFiltered)

          console.log(data)
        )
      )
      .catch(err => console.log("There was an error:" + err))
  

  useEffect(() => 
    fetchTopAnime()
  , [])

  // Fetch Anime details
  const fetchAnimeDetails = async () => 
    setAnimeReq( fetching: true )

    const response = await fetch(`$API/$props.match.params.animeId`)
    const data = await response.json()

    console.log(data);
    setAnimeReq( fetching: false, anime: data ) // set initial state to hold data from our API call
  


  // Fetch searched Anime
  const handleSubmit = async (e) => 
    e.preventDefault()

    const animeQuery = e.target.elements.anime.value
    const response = await fetch(`$API/search/anime?q=$animeQuery&page=1`)
    // const response2 = await fetch(`$API/top/anime/1/movie`)

    const animeData = await response.json()
    // const topAnime = await response2.json()

    setDataItems(animeData.results)
    setAnimeSearched(!animeSearched)

    props.history.push('dashboard')
  

  const  fetching, anime  = animeReq;

  return (
    <AnimeContext.Provider value=
      topTv,
      setTopTv,
      topAiring,
      setTopAiring,
      topUpcoming,
      setTopUpcoming,
      dataItems,
      setDataItems,
      animeSearched,
      setAnimeSearched,
      fetching,
      anime,
      fetchTopAnime,
      fetchAnimeDetails,
      handleSubmit
    >
      props.childen
    </AnimeContext.Provider>
  )


export  AnimeProvider, AnimeContext 

App.js

import React,  Component  from 'react';

import styled,  ThemeProvider  from 'styled-components';
import theme from './config/theme';
import  BrowserRouter as Router, Switch, Route  from 'react-router-dom'
import  AnimeProvider  from './store/AnimeContext'

import Header from './Components/Header';
import HomePage from './Components/Home Page/HomePage';
import AnimeDetails from './Components/AnimeDetails';
import AnimeCard from './Components/AnimeCard/AnimeCard'


class App extends Component 
  render() 
    return (
      <AnimeProvider>
        <Router>
          <ThemeProvider theme=theme>
            <AppWrapper>
              <Header />
              <Switch>
                <Route path='/' exact component=HomePage />
                <Route path='/dashboard' exact component=AnimeCard />
                <Route path='/:animeId' component=AnimeDetails />
              </Switch>
            </AppWrapper>
          </ThemeProvider>
        </Router>
      </AnimeProvider>
    );
  


const AppWrapper = styled.div`
  text-align: center;
  font-size: calc(10px + 1vmin);
  color: white;
        `

export default App;

主页组件

import React,  useContext  from 'react'
import styled from 'styled-components'

import  TopAnime  from './TopAnime';
import  AnimeContext  from '../../store/AnimeContext'

const HomePage = () => 
  const [topTv, topAiring, topUpcoming,] = useContext(AnimeContext)

  return (
    <AnimeContext.Consumer>
      <HomeWrapper>
        <TopAni>
          topTv.length > 0 ? <TopAniTitle>Top TV</TopAniTitle> : null
          topTv.map((item, index) => (
            <TopAnime
              key=index
              image=item.image_url
              title=item.title
              item=item
            />
          ))
        </TopAni>

        <TopAni>
          topAiring.length > 0 ? <TopAniTitle>Top Airing</TopAniTitle> : null
          topAiring.map((item, index) => (
            <TopAnime
              key=index
              image=item.image_url
              title=item.title
              item=item
            />
          ))
        </TopAni>

        <TopAni>
          topUpcoming.length > 0 ? <TopAniTitle>Top Upcoming</TopAniTitle> : null
          topUpcoming.map((item, index) => (
            <TopAnime
              key=index
              image=item.image_url
              title=item.title
              item=item
            />
          ))
        </TopAni>
      </HomeWrapper>
    </AnimeContext.Consumer>
  );


const HomeWrapper = styled.div`
  height: 100%;
  padding: 6rem 4.5rem;
  color: $props => props.theme.colors.white;
`

const TopAni = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-template-rows: auto;
  padding: 1rem;
`

const TopAniTitle = styled.h2`
  grid-column: 1 / -1;
  justify-self: start;
`

export default HomePage

如果我将AnimeProvider 移动到Header 下方,我可以像这样查看标题:

 return (
      <Router>
          <ThemeProvider theme=theme>
            <AppWrapper>
              <Header />
              <Switch>
        <AnimeProvider>
                <Route path='/' exact component=HomePage />
                <Route path='/dashboard' exact component=AnimeCard />
                <Route path='/:animeId' component=AnimeDetails />
      </AnimeProvider>
              </Switch>
            </AppWrapper>
          </ThemeProvider>
        </Router>
    );

所以我要么遗漏了一些重要的东西,要么我不了解 Context 的工作原理和/或 React Router。

【问题讨论】:

【参考方案1】:

您在 AnimeProvider 中有错字 - 它应该呈现 props.children 而不是 props.childen

【讨论】:

我不敢相信我犯了这样一个微不足道的错误。非常感谢您的时间和帮助。【参考方案2】:

AnimeContext.Consumer 需要一个 function 作为子级,如果您使用的是useContext,则无需首先使用AnimeContext.Consumer 包装您的组件。

此外,由于您的提供者传递了一个对象,那么useContext 将返回一个对象,因此您需要对象解构而不是数组解构。

const HomePage = () => 
  const  topTv, topAiring, topUpcoming  = useContext(AnimeContext)

  return (
     <HomeWrapper>
       <TopAni>
        topTv.length > 0 ? <TopAniTitle>Top TV</TopAniTitle> : null
        topTv.map((item, index) => (
          <TopAnime
            key=index
            image=item.image_url
            title=item.title
            item=item
          />
        ))
      </TopAni>
      ...
      </HomeWrapper>
  

【讨论】:

我进行了更改,但我的HomePage 仍然是空白的。什么都没有渲染。 另外,你有没有从Switch内部移动AnimeProvider 当我将它作为父级移到顶部时,没有任何渲染。当它位于Switch 正下方时,唯一呈现的是Header 这里是我的回购链接Repo

以上是关于React Context 不渲染组件的主要内容,如果未能解决你的问题,请参考以下文章

react面试题——面试必备

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

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

为啥我的组件在使用 React 的 Context API 和 useEffect 挂钩时会渲染两次?

React Context Provider 所有子级重新渲染

React组件性能优化总结