Вызов метода дочернего класса из родительского
У меня есть два компонента:
- Родительский компонент
- Дочерний компонент
Я пытался вызвать метод дочернего компонента из родительского, но не смог добиться результата. Вот что я пробовал:
class Parent extends Component {
render() {
return (
<Child>
<button onClick={Child.getAlert()}>Click</button>
</Child>
);
}
}
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
Есть ли способ вызвать метод дочернего компонента из родительского?
Примечание: Родительский и дочерний компоненты находятся в двух разных файлах.
5 ответ(ов)
Вы можете использовать другой подход в данном случае:
class Parent extends Component {
render() {
return (
<div>
<Child setClick={click => this.clickChild = click}/>
<button onClick={() => this.clickChild()}>Click</button>
</div>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.getAlert = this.getAlert.bind(this);
}
componentDidMount() {
this.props.setClick(this.getAlert);
}
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
Что происходит в этом коде: метод clickChild
родительского компонента устанавливается во время монтирования дочернего компонента. Таким образом, когда вы нажимаете кнопку в родителе, вызывается clickChild
, которая в свою очередь вызывает getAlert
дочернего компонента.
Этот подход также работает, если ваш дочерний компонент обернут в connect()
, и вам не нужно использовать хитрость с getWrappedInstance()
.
Обратите внимание, что вы не можете использовать onClick={this.clickChild}
в родительском компоненте, потому что когда родитель рендерится, дочерний компонент еще не смонтирован и this.clickChild
еще не присвоен. Использовать onClick={() => this.clickChild()}
вполне допустимо, потому что в момент нажатия кнопки this.clickChild
уже должен быть присвоен.
В вашем коде вы используете useState
для хранения счетчика обновлений (refresh
), который передается в дочерний компонент Children
. Внутри Children
вы используете useEffect
для выполнения функции performRefresh
каждый раз, когда значение refresh
изменяется. Это нормальный и распространенный подход для механизмов обновления в React.
Однако, если вы ищете альтернативный метод, вы можете рассмотреть следующие подходы:
1. Использовать Callback
Вместо передачи refresh
как пропса, вы можете передать функцию для обновления:
// Parent
const handleRefresh = () => {
performRefresh(); // вызываем функцию обновления, когда это необходимо
};
<Button onClick={handleRefresh} />
<Children onRefresh={handleRefresh} />
В этом случае, каждый раз, когда кнопка нажимается, performRefresh
вызывается напрямую.
2. Избегать лишнего состояния
Если вам не нужно хранить состояние обновления, можно напрямую вызывать функцию обновления из родительского компонента:
// Parent
const performRefresh = () => {
// Логика обновления, которую можно вынести сюда
};
<Button onClick={performRefresh} />
<Children onRefresh={performRefresh} />
3. Использовать Context для управления состоянием
Если у вас несколько дочерних компонентов, которым нужно использовать функцию обновления, можно использовать Context API для более гибкого управления состоянием:
// Создайте контекст
const RefreshContext = createContext();
const Parent = () => {
const handleRefresh = () => {
performRefresh();
};
return (
<RefreshContext.Provider value={handleRefresh}>
<Button onClick={handleRefresh} />
<Children />
</RefreshContext.Provider>
);
};
// Дочерний компонент
const Children = () => {
const performRefresh = useContext(RefreshContext);
useEffect(() => {
// Логика, которая зависит от состояния
performRefresh();
}, []);
return <div>...</div>;
};
Таким образом, вы облегчите взаимодействие между компонентами и сделаете код более управляемым.
Вот четыре возможные комбинации, которые могут возникнуть:
1. Класс Родитель | Хук Ребенок
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return (<View>
<Child ref={this.myRef}/>
<Button title={'позвони мне'}
onPress={() => this.myRef.current.childMethod()}/>
</View>)
}
}
const Child = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
childMethod()
}
}))
function childMethod() {
console.log('позвони мне')
}
return (<View><Text> Я ребенок</Text></View>)
})
2. Хук Родитель | Класс Ребенок
function Parent(props) {
const myRef = useRef()
return (<View>
<Child ref={myRef}/>
<Button title={'позвони мне'}
onPress={() => myRef.current.childMethod()}/>
</View>)
}
class Child extends React.Component {
childMethod() {
console.log('позвони мне')
}
render() {
return (<View><Text> Я ребенок</Text></View>)
}
}
3. Хук Родитель | Хук Ребенок
function Parent(props) {
const myRef = useRef()
return (<View>
<Child ref={myRef}/>
<Button title={'позвони мне'}
onPress={() => myRef.current.childMethod()}/>
</View>)
}
const Child = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
childMethod()
}
}))
function childMethod() {
console.log('позвони мне')
}
return (<View><Text> Я ребенок</Text></View>)
})
4. Класс Родитель | Класс Ребенок
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return (<View>
<Child ref={this.myRef}/>
<Button title={'позвони мне'}
onPress={() => this.myRef.current.childMethod()}/>
</View>)
}
}
class Child extends React.Component {
childMethod() {
console.log('позвони мне')
}
render() {
return (<View><Text> Я ребенок</Text></View>)
}
}
Эти примеры показывают, как можно использовать различные подходы к созданию родительского и дочернего компонентов в React, как с помощью классов, так и с использованием хуков. Выбор подхода зависит от ваших предпочтений и требований проекта.
Я не был удовлетворён ни одним из представленных здесь решений. На самом деле, существует очень простое решение, которое можно реализовать с помощью чистого JavaScript без использования какой-либо функциональности React, кроме базового объекта props. Это решение позволяет двустороннее взаимодействие (родитель → ребёнок, ребёнок → родитель). Вам нужно передать объект из родительского компонента в дочерний компонент. Этот объект я называю «двусторонняя ссылка» или просто biRef. По сути, объект содержит ссылки на методы родителя, которые он хочет предоставить. А дочерний компонент добавляет методы в этот объект, которые может вызывать родитель. Пример:
// Родительский компонент.
function MyParentComponent(props) {
function someParentFunction() {
// Этот метод может вызывать дочерний компонент.
}
function onButtonClick() {
// Вызовем функцию из дочернего компонента.
biRef.someChildFunction();
}
// Добавьте все функции здесь, которые может вызывать дочерний компонент.
var biRef = {
someParentFunction: someParentFunction
}
return <div>
<MyChildComponent biRef={biRef} />
<Button onClick={onButtonClick} />
</div>;
}
// Дочерний компонент
function MyChildComponent(props) {
function someChildFunction() {
// Этот метод может вызывать родительский компонент.
}
function onButtonClick() {
// Вызовем родительскую функцию.
props.biRef.someParentFunction();
}
// Добавьте все функции дочернего компонента в props.biRef, которые хотите,
// чтобы родитель мог вызывать.
props.biRef.someChildFunction = someChildFunction;
return <div>
<Button onClick={onButtonClick} />
</div>;
}
Другим преимуществом этого решения является возможность добавления большего количества функций в родительский и дочерний компоненты при передаче их только через одно свойство.
Улучшение этого кода заключается в том, что вместо того, чтобы добавлять функции родителя и дочернего компонента непосредственно в объект biRef, лучше добавлять их в подмодули. Функции родителя следует добавлять в элемент с именем "parent", а функции дочернего компонента – в элемент с именем "child".
// Родительский компонент.
function MyParentComponent(props) {
function someParentFunction() {
// Этот метод может вызывать дочерний компонент.
}
function onButtonClick() {
// Вызовем функцию из дочернего компонента.
biRef.child.someChildFunction();
}
// Добавьте все функции, которые может вызывать дочерний компонент.
var biRef = {
parent: {
someParentFunction: someParentFunction
}
}
return <div>
<MyChildComponent biRef={biRef} />
<Button onClick={onButtonClick} />
</div>;
}
// Дочерний компонент
function MyChildComponent(props) {
function someChildFunction() {
// Этот метод может вызывать родительский компонент.
}
function onButtonClick() {
// Вызовем родительскую функцию.
props.biRef.parent.someParentFunction();
}
// Добавьте все функции дочернего компонента в props.biRef, которые хотите,
// чтобы родитель мог вызывать.
props.biRef.child = {
someChildFunction: someChildFunction
};
return <div>
<Button onClick={onButtonClick} />
</div>;
}
Размещая функции родителя и дочернего компонента в отдельных элементах объекта biRef, вы получите чёткое разделение между ними и легко увидите, какие функции принадлежат родителю, а какие – дочернему компоненту. Это также помогает избежать случайного перезаписывания дочерним компонентом функции родителя, если обе функции имеют одинаковое имя.
Последнее, что стоит упомянуть: обратите внимание, что родительский компонент создаёт объект biRef с помощью var, тогда как дочерний компонент получает к нему доступ через объект props. Может возникнуть соблазн не определять объект biRef в родителе и обращаться к нему через параметр props в дочернем компоненте (что может иметь место в иерархии UI-элементов). Это рискованно, потому что дочерний компонент может подумать, что функция, которая находится у родителя, принадлежит ему, хотя на самом деле она может принадлежать "дедушке". В этом нет ничего плохого, пока вы осознаёте это. Если у вас нет причин поддерживать какую-либо иерархию выше, чем родитель/дочерний компонент, лучше создавать biRef в родительском компоненте.
Вы используете хук useEffect
для того, чтобы избежать определенных сложностей, теперь вы передаете переменную в дочерний компонент вот так:
import { useEffect, useState } from "react";
export const ParentComponent = () => {
const [trigger, setTrigger] = useState(false);
return (
<div onClick={() => { setTrigger(trigger => !trigger); }}>
<ChildComponent trigger={trigger}></ChildComponent>
</div>
);
};
export const ChildComponent = (props) => {
const triggerInvokedFromParent = () => {
console.log('TriggerInvokedFromParent');
};
useEffect(() => {
triggerInvokedFromParent();
}, [props.trigger]);
return <span>ChildComponent</span>;
};
В этом примере родительский компонент ParentComponent
использует состояние trigger
, которое переключается при клике на div
. Это состояние передается в дочерний компонент ChildComponent
. Внутри дочернего компонента, useEffect
следит за изменениями props.trigger
и вызывает функцию triggerInvokedFromParent()
каждый раз, когда trigger
обновляется. Это позволяет вам реагировать на изменения состояния в родительском компоненте.
Как программно выполнять навигацию с помощью React Router?
Как условно добавлять атрибуты к компонентам React?
Как установить фокус на поле ввода после рендеринга?
Метод set в useState не отражает изменения немедленно
В чем разница между React Native и React?