簡體   English   中英

React - 如何使用另一個組件重新渲染一個組件?

[英]React - How to re-render a component using another component?

我有一個NavBar組件,其中包含動態生成的鏈接列表(這些鏈接是在查詢我的后端的某些categories后生成的)。 這些鏈接存儲在 NavBar 的子組件中,稱為DrawerMenu

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

在我的Category組件中,我有一個刪除類別的“刪除”function。 一旦我刪除了一個類別,我想在NavBar中刪除指向它的鏈接。 我將如何 go 這樣做?

對於進一步的上下文,我的組件如下所示:

DrawerMenu 組件

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(`/`)
    }
    
    ...
}

有兩種常見的方法:您可以使用 state 管理工具,如Redux或將您的 state 作為道具傳遞到組件樹中。

Redux 常用於多個組件依賴於同一個 state 或依賴於 state 的組件向下傳遞好幾層時,傳遞它會很麻煩。

我假設您的組件樹不是很大,所以我將創建一個簡單的示例,將道具向下傳遞。

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(`/`)
    }
    
    ...
}

我建議閱讀有關 state 管理的內容,它是 React 中的核心概念,您將在任何地方使用它。 例如 Redux 和上下文 API。

不知道為什么 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>
     }
}

編輯:

我看到您正在混合 class 和功能組件,這很好。 您應該查看這篇文章,了解如何在其中任何一個中使用上下文 api - 在功能組件中您通常使用useContext掛鈎,而在 class 組件中您將使用消費者。

在刪除請求完成后,我會刷新來自服務器的類別列表。

我會這樣做:

  • 我會讓抽屜組件不那么聰明,讓它接收菜單項列表。
<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 來做。 最好在頂層組件中有智能/獲取/服務器端邏輯,然后將 props 傳遞給啞組件。

PS。 還有一個很好的鈎子可以讓獲取更容易: https://github.com/doasync/use-promise

我目前使用我發現的 usePromise 掛鈎的自定義版本,因為我添加了一些有趣的功能。 如果您願意,我可以分享它,但我不想在答案中添加噪音。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM