【问题标题】:React - How to re-render a component using another component?React - 如何使用另一个组件重新渲染一个组件?
【发布时间】:2021-03-16 14:31:21
【问题描述】:

我有一个NavBar 组件,它有一个动态生成的链接列表(这些链接是在我的后端查询一些categories 后生成的)。这些链接存储在 NavBar 的子组件中,称为 DrawerMenu

NavBar 是主 App.js 组件的子组件。

在我的Category 组件中,我有一个删除类别的“删除”功能。一旦我删除了一个类别,我想在NavBar 中删除指向它的链接。我该怎么做呢?

为了进一步了解,我的组件如下所示:

抽屉菜单组件

class DrawerMenu extends Component {
  state = {
    menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
  }

  getData = (query) => {
    // Query backend for category data and set it to this.state.menuItems
  }

  componentDidMount() {
    this.getData(menuItemsQuery)
  }

  render() {
    const { classes, handleDrawerClose, open } = this.props
    const { menuItems } = this.state

    const drawer = (classes, handleDrawerClose) => (
      <div>
          ...

          {
            menuItems.map((menuItem, index) => (
              <Link color="inherit" key={index} to={menuItem.link} className={classes.drawerLink} component={RouterLink}>
                <ListItem button className={classes.drawerListItem} onClick={handleDrawerClose}>
                  <ListItemText primary={menuItem.name} />
                </ListItem>
              </Link>
            ))
          }
          
          ...
      </div>
    )
    
    ...

    return (
      <div>
        <Drawer
          variant="temporary"
          anchor='left'
          open={open}
          onClose={handleDrawerClose}
          classes={{
            paper: `${open ? classes.drawerOpen : null} ${!open ? classes.drawerClose : null}`,
          }}
          ModalProps={{
            keepMounted: true, // Better open performance on mobile.
          }}
        >
          {drawer(classes, handleDrawerClose)}
        </Drawer>
      </div>
    )
  }
}

导航栏组件

function PrimarySearchAppBar(props) {
    return (
        <div className={classes.grow}>
            
            ...
            
            <DrawerMenu
                classes={classes}
                handleDrawerClose={handleDrawerClose}
                open={open}
            />
            
            ...
        </div>
    )
}

类别组件

class Category extends Component {
    ...
    
    deleteCategory = async () => {
        // Code to request backend to delete category
        this.props.history.push(`/`)
    }
    
    ...
}

【问题讨论】:

  • 您可以使用 Content API reactjs.org/docs/context.html
  • 您可以添加另一个状态来标记服务器请求已发出,您可以在删除后拉取新数据

标签: javascript reactjs material-ui frontend


【解决方案1】:

有两种常见的方法:您可以使用状态管理工具,例如Redux,或者将您的状态作为道具传递到组件树中。

当多个组件依赖于同一个 state 或者依赖于一个 state 的组件有好几层深时,通常会使用 Redux,因此将其作为 props 向下传递会很麻烦。

我假设您的组件树不是很大,所以我将创建一个简单的示例,将 props 向下传递。

class DrawerMenu extends Component {
  // We're gonna manage the state here, so the deletion
  // will actually be handled by this component
  state = {
    menuItems: [] // Takes a series of objects of the shape { name: "", link: "" }
  }

  handleDelete = (id) => {
    let updatedMenuItem = [...this.state.menuItems]; //Create a copy
    updatedMenuItem = updatedMenuItem(item => item.id !== id) // Remove the 
deleted item
    this.setState({
       menuItems: updatedMenuItem
    })    
   
  }
  ...

   // Then wherever you render the category component
   <Category handleDelete = {handleDelete}/> //Pass a reference to the delete method

}


类别组件

 class Category extends Component {
    ...
    
    deleteCategory = async () => {
        // Code to request backend to delete category
        this.props.handleDelete(categoryId) //Pass the id of the category
        this.props.history.push(`/`)
    }
    
    ...
}

我建议阅读有关状态管理的内容,它是 React 的核心概念,您将在任何地方使用它。例如 Redux 和 Context API。

【讨论】:

    【解决方案2】:

    不知道为什么 Dennis Vash 删除了他们的答案,他们是正确的,但在解决方案中可能描述性不够。

    您删除类别的方式不是从类别组件内部调用后端本身,因为这样导航栏不知道您进行了调用,而是调用由双方共享的祖先中的回调类别组件和导航栏删除一个类别,然后从服务器重新请求类别列表。在下面的例子中,这个被共享的祖先是MyCategoriesProvider

    因为类别组件可能在树中的位置(或多个位置)与 NavBar 大不相同,所以最好使用上下文。

    老实说,这里是 redux 的好去处,但我不会向你推广 redux,而只是演示一个 Context 解决方案。

    // We're going to create a context that will manage your categories
    // The only job of this context is to hold the current categories, 
    // and supply the updating functions. For brevity, I'll just give 
    // it a handleDelete function.
    // Ideally, you'd also store the status of the request in this context
    // as well so you could show loaders in the app, etc
    
    import { createContext } from 'react';
    
    // export this, we'll be using it later
    export const CategoriesContext = createContext();
    
    // export this, we'll render it high up in the app
    // it will only accept children
    export const MyCategoriesProvider = ({children}) => {
    
       // here we can add a status flag in case we wanted to show a spinner
       // somewhere down in your app
       const [isRequestingCategories,setIsRequestingCategories] = useState(false);
    
       // this is your list of categories that you got from the server
       // we'll start with an empty array
       const [categories,setCategories] = useState([]);
    
       const fetch = async () => {
          setIsRequestingCategories(true);
          setCategories(await apiCallToFetchCategories());
          setIsRequestingCategories(false);
       }
    
       const handleDelete = async category => {
           await apiCallToDeleteCategory(category);
           // we deleted a category, so we should re-request the list from the server
           fetch();
       }
    
       useEffect(() => {
          // when this component mounts, fetch the categories immediately
          fetch();
    
          // feel free to ignore any warnings if you're using a linter about rules of hooks here - this is 100% a "componentDidMount" hook and doesn't have any dependencies
       },[]);
    
       return <CategoriesContext.Provider value={{categories,isRequestingCategories,handleDelete}}>{children}</CategoriesContext.Provider>
    
    }
    
    // And you use it like this:
    
    const App = () => {
      return (
        <MyCategoriesProvider>
          <SomeOtherComponent>
          <SomeOtherComponent> <- let's say your PrimarySearchBar is in here somewhere
          <SomeOtherComponent>
        </MyCategoriesProvider>
      )
    
    }
    
    // in PrimarySearchBar you'd do this:
    
    function PrimarySearchBar(props) => {
       const {categories} = useContext(CategoriesContext); // you exported this above, remember?
       
       // pass it as a prop to navbar, you could easily put the useContext hook inside of any component
       return <NavBar categories={categories}/>
    
    }
    
    
    // in your category component you could do this:
    
    class Category extends Component {
         render() {
            // Don't forget, categoriesContext is the thing you exported way up at the top
            <CategoriesContext.Consumer>
               {({handleDelete}) => {
                    return <button onClick={() => handleDelete(this.props.category)}>
               }}
            </CategoriesContext.Consumer>
         }
    }
    
    

    编辑:

    我看到您正在混合类和功能组件,这很好。您应该查看this article,了解如何在其中任何一个中使用上下文 API - 在功能组件中您通常使用 useContext 挂钩,而在类组件中您将使用消费者。

    【讨论】:

      【解决方案3】:

      删除请求完成后,我会刷新来自服务器的类别列表。

      我会这样做:

      • 我会让抽屉组件不那么聪明,让它接收菜单项列表。
      <DrawerMenu
          classes={classes}
          handleDrawerClose={handleDrawerClose}
          open={open}
          items={/* ... */}
      />
      

      这是一个重要的步骤,因为现在,要刷新呈现的项目列表,您只需传递另一个列表。以这种方式,服务器端逻辑与该组件保持断开连接。

      • 我不确定你在哪里渲染 Category 组件,但假设它在 PrimarySearchAppBar 之外渲染,似乎这个 menuItems 可能需要从上层传递给组件。我看到了 2 个解决方案:

        1. 我会从我请求类别的同一位置请求 menuItems:
      const App = props => {
          const [categories, setCategories] = React.useState([])
          const [menuItems, setMenuItems] = React.useState([])
      
          const fetchCategories = useCallback(()=> {
              yourApi.getCategories().then(categories => setCategories(categories))
          })
      
          const fetchMenuItems = useCallback(() => {
              yourApi.getMenuItems().then(menuItems => setMenuItems(menuItems))
          })
      
          useEffect(() => {
              fetchCategories()
          }, [])
      
          useEffect(() => {
             fetchMenuItems()
          }, [categories])
      
          const handleDeleteCategory = useCallback(idToDelete => {
              yourApi.deleteCategory(idToDelete).then(fetchCategories)
          })
      
          return (
              <div>
                   <PrimarySearchAppBar menuItems={menuItems}/>
                   <Categories categories={categories} onDeleteClick={handleDeleteCategory} />
              </div>
          )
      
      }
      
      1. 您可以做同样的事情,但如果您不想在此处拥有所有逻辑,请使用提供程序并使用内容 API。最好在顶层组件中包含 smart/fetches/server-side 逻辑,然后将 props 传递给哑组件。

      PS。 还有一个很好的钩子可以使获取更容易: https://github.com/doasync/use-promise

      我目前使用我发现的 usePromise 挂钩的自定义版本,因为我添加了一些有趣的功能。如果你愿意,我可以分享,但我不想在答案中添加噪音。

      【讨论】:

        猜你喜欢
        • 2020-08-09
        • 2019-04-19
        • 1970-01-01
        • 2021-08-17
        • 2018-01-26
        • 2019-07-03
        • 1970-01-01
        • 1970-01-01
        • 2023-01-30
        相关资源
        最近更新 更多