Состояние полностью приватно для компонента, объявившего его. Родительские компоненты не могут его изменить.
Хорошо! Я знаю, что и заголовок, и подзаголовок противоречат друг другу. В заголовке сказано, что мы можем изменить состояние родительского компонента, а в подзаголовке сказано, что нельзя, так как состояние является приватным для компонента, объявившего его. Второе утверждение верно. Государство является изолированным и частным. Состояние является локальным для экземпляра компонента на экране. Другими словами, если вы рендерите один и тот же компонент дважды, каждая копия будет иметь полностью изолированное состояние! Изменение одного из них не повлияет на другой. Состояние не привязано к конкретному вызову функции или месту в коде, но оно «локально» для определенного места на экране. Это позволяет добавлять состояние к любому компоненту или удалять его, не затрагивая остальные компоненты.
Однако изменение состояния дочернего элемента от родителя возможно. Позвольте мне подробно объяснить это на реальном примере. Я подготовил песочницу кода. Вы можете перейти по ссылке. У нас есть компонент Table со столбцом действий. В столбце действий есть компонент Popover со ссылкой для открытия модального окна. Как только мы откроем Modal, вы увидите, что Popover не закрывается автоматически; он срабатывает только по событию клика.
Чтобы управлять этим поведением Popover, мы используем свойство open
, которое представляет собой логическое значение, указывающее состояние Popover, открыто оно или закрыто. Мы определяем состояние и изменяем состояние с помощью onOpenChange
обратного вызова, выполняемого при изменении видимости карточки Popover. Здесь, если мы определим состояние в компоненте Таблица, как показано в приведенном ниже коде, состояние теперь будет общим для каждого экземпляра всплывающего окна, и у нас будет другое поведение, когда изменение состояния одного компонента всплывающего окна запускает все Popover для открытия и закрытия одновременно. Вы можете посетить codesandbox здесь для демонстрации.
// file: TableComponent.tsx const TableComponent = () => { const [open, setOpen] = useState(false); const handleOpenChange = (newOpen: boolean) => { setOpen(newOpen); }; const columns: ColumnsType<DataType> = [ { title: "Name", dataIndex: "name", key: "name", render: (text) => <a>{text}</a> }, { title: "Age", dataIndex: "age", key: "age" }, { title: "Address", dataIndex: "address", key: "address" }, { title: "Tags", key: "tags", dataIndex: "tags", render: (_, { tags }) => ( <> {tags.map((tag) => { let color = tag.length > 5 ? "geekblue" : "green"; if (tag === "loser") { color = "volcano"; } return ( <Tag color={color} key={tag}> {tag.toUpperCase()} </Tag> ); })} </> ) }, { title: "Action", key: "action", render: (_, record) => ( <Popover content={<Actions />} trigger="click" open={open} onOpenChange={handleOpenChange} > <EllipsisOutlined style={{ fontSize: 16 }} /> </Popover> ) } ]; return <Table columns={columns} dataSource={data} pagination={false} />; };
Чтобы решить эту проблему, мы можем поместить всплывающее окно в отдельный компонент, у которого есть внутреннее состояние, которое само отслеживает видимость. Демо представлено здесь. Тем не менее, мы еще не решили первую проблему, которую мы обсуждали, с автоматическим закрытием всплывающего окна при открытом модальном окне. Теперь у нас есть контроль над состоянием всплывающего окна. Все, что осталось сделать, это установить состояние всплывающего окна в false, когда мы нажимаем на значок Open Modal
. Но как мы можем этого достичь? Поскольку состояние создается в дочернем компоненте (PopoverComponent) и для его обновления из родительского компонента (TableComponent), мы можем использовать атрибут ref
. Мы предоставляем функцию setOpen
родительскому компоненту, используя useImperativeHandle
и ref
.
В React обычно рекомендуется сохранять как можно больше состояний и логики в компонентах, которые в них нуждаются. Однако бывают случаи, когда родительскому компоненту необходимо иметь возможность вызвать метод или получить доступ к свойству дочернего компонента. Вот тут и появляется useImperativeHandle
.
useImperativeHandle
— это хук React, который позволяет вам предоставлять определенные функции или методы дочернего компонента его родительскому компоненту. Он принимает два аргумента: объект ref и функцию, которая возвращает объект, содержащий открытые методы. Обновленный компонент всплывающего окна выглядит примерно так:
// file: PopoverComponent.tsx import React, { useState, useImperativeHandle } from "react"; import { Popover, PopoverProps } from "antd"; export type OpenState = { setOpen: (visible: boolean) => void; }; const PopoverComponent = React.forwardRef<OpenState, PopoverProps>( ({ ...rest }, ref) => { const [open, setOpen] = useState(false); // expose the setOpen function to the parent component using useImperativeHandle useImperativeHandle(ref, () => ({ setOpen(visible: boolean) { setOpen(visible); } })); return ( <Popover open={open} onOpenChange={(newOpen: boolean) => setOpen(newOpen)} {...rest} > {rest.children} </Popover> ); } ); export { PopoverComponent as Popover };
Мы определили тип OpenState
, который используется для определения интерфейса объекта ref
, который возвращается из React.forwardRef
. Интерфейс OpenState
определяет одно свойство, setOpen
, которое представляет собой функцию, которая принимает логическое значение и возвращает void
. Хук useImperativeHandle
используется для предоставления функции setOpen
родительскому компоненту с помощью объекта ref
. Эта функция может быть вызвана родительским компонентом для установки значения состояния open
в пределах PopoverComponent
.
// file: TableComponent.tsx import { useRef, useState } from "react"; .... const TableComponent = () => { const popoverOpenRef = useRef(null); .... const columns: ColumnsType<DataType> = [ ...., { title: "Action", key: "action", render: (_, record) => ( <Popover ref={popoverOpenRef} content={ <Actions showModal={showModal} popoverOpenRef={popoverOpenRef} /> } trigger="click" > <EllipsisOutlined style={{ fontSize: 16 }} /> </Popover> ) } ]; .... // file: Actions.tsx import { OpenState } from "./PopoverComponent"; interface ActionsProps { showModal: () => void; popoverOpenRef: React.RefObject<OpenState>; } const Actions: React.FC<ActionsProps> = ({ showModal, popoverOpenRef }) => ( <> <p style={{ fontSize: "16px", cursor: "pointer", fontWeight: 700 }} onClick={() => { showModal(); popoverOpenRef.current?.setOpen(false); }} > Open Modal </p> </> ); export default Actions;
В компоненте Table мы создаем popoverOpenRef
, используя useRef
, и передаем его в PopoverComponent
, используя реквизит ref
. Наконец, мы передаем свойство popoverOpenRef
компоненту Actions
, а в обработчике onClick
мы вызываем функцию setOpen
для popoverOpenRef
, чтобы обновить состояние PopoverComponent
до false.
Компонент
Actions
типизирован с использованием интерфейсаActionsProps
, который определяет ожидаемые типы свойств. СвойствоshowModal
определяется как функция, которая не принимает аргументов и возвращает void. СвойствоpopoverOpenRef
определяется как объект React Ref, который ссылается на типOpenState
.
Финальное демо проекта можно найти здесь.
Заключение
Таким образом, первая проблема, которую мы обсуждали с автоматическим закрытием всплывающего окна, когда модальное окно открыто, решена, и в этом примере мы получили краткий обзор того, как мы можем обновить внутреннее состояние дочернего элемента из родительского компонента с помощью useImperativeHandle
react hook. .
Удачного кодирования! Увидимся в следующей статье.