Chame um método de componente React de fora

92

Eu quero chamar um método exposto por um componente React da instância de um Elemento React.

Por exemplo, neste jsfiddle . Eu quero chamar o alertMessagemétodo da HelloElementreferência.

Existe uma maneira de fazer isso sem precisar escrever invólucros adicionais?

Editar (código copiado do JSFiddle)

<div id="container"></div>
<button onclick="onButtonClick()">Click me!</button>
var onButtonClick = function () {

    //call alertMessage method from the reference of a React Element! Something like HelloElement.alertMessage()
    console.log("clicked!");
}

var Hello = React.createClass({displayName: 'Hello',

    alertMessage: function() {
        alert(this.props.name);                             
    },

    render: function() {
        return React.createElement("div", null, "Hello ", this.props.name);
    }
});

var HelloElement = React.createElement(Hello, {name: "World"});

React.render(
    HelloElement,
    document.getElementById('container')
);
Guerreiro Rico
fonte
3
Não é o ideal, mas JSFiddle é comum o suficiente para não justificar uma votação negativa.
Jeff Fairley
Estou me perguntando qual poderia ser o seu caso de uso que justificaria tal coisa. Esta não é uma boa maneira de projetar seu aplicativo imediatamente. Se você precisar reutilizar algo, crie um auxiliar comum separado em um terceiro arquivo e use-o para o botão e também para o componente de reação.
sabor a chá de

Respostas:

55

Existem duas maneiras de acessar uma função interna. Um nível de instância, como você deseja, outro nível estático.

Instância

Você precisa chamar a função no retorno de React.render. Ver abaixo.

Estático

Dê uma olhada em ReactJS Statics . Observe, no entanto, que uma função estática não pode acessar dados no nível da instância, e thisdeveria undefined.

var onButtonClick = function () {
    //call alertMessage method from the reference of a React Element! 
    HelloRendered.alertMessage();
    //call static alertMessage method from the reference of a React Class! 
    Hello.alertMessage();
    console.log("clicked!");
}

var Hello = React.createClass({
    displayName: 'Hello',
    statics: {
        alertMessage: function () {
            alert('static message');
        }
    },
    alertMessage: function () {
        alert(this.props.name);
    },

    render: function () {
        return React.createElement("div", null, "Hello ", this.props.name);
    }
});

var HelloElement = React.createElement(Hello, {
    name: "World"
});

var HelloRendered = React.render(HelloElement, document.getElementById('container'));

Então faça HelloRendered.alertMessage().

Jeff Fairley
fonte
13
Observe que o uso do valor de retorno de renderização é considerado obsoleto e deve ser removido em versões futuras para permitir melhorias de desempenho. A maneira com suporte de obter uma referência para o objeto da instância do componente é adicionar uma propriedade refque é uma função chamada com a instância como um parâmetro. Isso também permite que você acesse objetos que não estão no nível superior, por exemplo, se você estiver renderizando, <MuiThemeProvider><Hello ref={setHelloRef} /></MuiThemeProvider>obterá a referência correta passada para sua setHelloReffunção em vez de uma para ela MuiThemeProvider.
Periata Breatta
43

Você pode fazer como

import React from 'react';

class Header extends React.Component{

    constructor(){
        super();
        window.helloComponent = this;
    }

    alertMessage(){
       console.log("Called from outside");
    }

    render(){

      return (
      <AppBar style={{background:'#000'}}>
        Hello
      </AppBar>
      )
    }
}

export default Header;

Agora, de fora deste componente, você pode chamar assim abaixo

window.helloComponent.alertMessage();
Kushal Jain
fonte
1
Na verdade, simples e funcional! Como deveria ser. Estou muito impressionado com a forma como é simples; realmente não pensei. Provavelmente, essa abordagem não funcionará tão bem se houver mais e mais componentes que você deve. Obrigado!
Leonardo Maffei
6
Adicionar uma variável global não é uma boa solução. Mais sobre por que as variáveis ​​globais são ruins aqui: wiki.c2.com/?GlobalVariablesAreBad
Salvatore Zappalà
3
Obrigado por votar! Sim, as variáveis ​​globais não são boas, mas é uma maneira de resolver o seu problema.
Kushal Jain
Esta é exatamente a solução pragmática e simples de que eu precisava, obrigado!
Florent Destremau
2
funcionou, mas não funcionará se você tiver o mesmo componente múltiplo na mesma página.
gaurav
24

Eu fiz algo assim:

class Cow extends React.Component {

    constructor (props) {
        super(props);
        this.state = {text: 'hello'};
    }

    componentDidMount () {
        if (this.props.onMounted) {
            this.props.onMounted({
                say: text => this.say(text)
            });
        }
    }

    render () {
        return (
            <pre>
                 ___________________
                < {this.state.text} >
                 -------------------
                        \   ^__^
                         \  (oo)\_______
                            (__)\       )\/\
                                ||----w |
                                ||     ||
            </pre>
        );
    }

    say (text) {
        this.setState({text: text});
    }

}

E então em outro lugar:

class Pasture extends React.Component {

    render () {
        return (
            <div>
                <Cow onMounted={callbacks => this.cowMounted(callbacks)} />
                <button onClick={() => this.changeCow()} />
            </div>
        );
    }

    cowMounted (callbacks) {
        this.cowCallbacks = callbacks;
    }

    changeCow () {
        this.cowCallbacks.say('moo');
    }

}

Não testei esse código exato, mas segue as linhas do que fiz em um projeto meu e funciona muito bem :). Claro que este é um exemplo ruim, você deve apenas usar adereços para isso, mas no meu caso o subcomponente fez uma chamada de API que eu queria manter dentro desse componente. Nesse caso, esta é uma boa solução.

gitaarik
fonte
5
Acho que você quis dizerthis.cowCallbacks.say('moo')
Steven
BTW, é possível passar thispara callback (que será uma instância do Cow) em vez de callbacks.
WebBrother de
@WebBrother Sim, mas isso seria ainda mais hacky
gitaarik de
6

Com o rendermétodo potencialmente substituindo o valor retornado, a abordagem recomendada agora é anexar uma referência de retorno de chamada ao elemento raiz. Como isso:

ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));

que podemos acessar usando window.helloComponent, e qualquer um de seus métodos pode ser acessado com window.helloComponent.METHOD.

Aqui está um exemplo completo:

var onButtonClick = function() {
  window.helloComponent.alertMessage();
}

class Hello extends React.Component {
  alertMessage() {
    alert(this.props.name);
  }

  render() {
    return React.createElement("div", null, "Hello ", this.props.name);
  }
};

ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
<button onclick="onButtonClick()">Click me!</button>

Brett DeWoody
fonte
Melhor colocar a referência no estado local em vez de no objeto da janela ... ref={component => {this.setState({ helloComponent: component; })}} Então, no manipulador de cliques ... this.state.helloComponent.alertMessage();
Bill Dagg
Continuando com meu comentário anterior ... ou, melhor ainda, apenas coloque o método alertMessage do componente no estado.
Bill Dagg de
Quando tento fazer isso, consigo Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?e não liga o componente à janela
Guerrilha,
4
class AppProvider extends Component {
  constructor() {
    super();

    window.alertMessage = this.alertMessage.bind(this);
  }

  alertMessage() {
    console.log('Hello World');
 }
}

Você pode chamar esse método da janela usando window.alertMessage().

Mygel Bergstresser
fonte
Isso funcionaria, mas adicionar uma variável global não é uma boa solução, por muitos motivos. Você pode encontrar mais informações sobre por que as variáveis ​​globais são ruins aqui: wiki.c2.com/?GlobalVariablesAreBad
Salvatore Zappalà
3

Se você estiver no ES6, apenas use a palavra-chave "estática" em seu método do exemplo que seria o seguinte: static alertMessage: function() { ...
},

Espero poder ajudar alguém aí :)

Darmis
fonte
Você não pode alcançar adereços ou estado em uma função estática.
Serdar Değirmenci
Ok, sim, mas a questão era acessar a função alertMessage () para que você pudesse usar HelloElement.alertMessage ().
darmis
Geralmente, chamar uma função sem usar adereços e estado não tem efeito. Mas, como você está certo sobre alcançar função, estou removendo meu voto
Serdar Değirmenci
2

Você pode simplesmente adicionar um onClickmanipulador ao div com a função (onClick é a implementação do próprio React de onClick) e pode acessar a propriedade entre { }chaves e sua mensagem de alerta aparecerá.

No caso de você desejar definir métodos estáticos que podem ser chamados na classe do componente - você deve usar estática. Apesar:

"Os métodos definidos neste bloco são estáticos, o que significa que você pode executá-los antes que qualquer instância do componente seja criada, e os métodos não têm acesso aos adereços ou estado de seus componentes. Se você quiser verificar o valor dos adereços em um estático , faça o chamador passar os adereços como um argumento para o método estático. " ( fonte )

Alguns exemplos de código:

    const Hello = React.createClass({

        /*
            The statics object allows you to define static methods that can be called on the component class. For example:
        */
        statics: {
            customMethod: function(foo) {
              return foo === 'bar';
            }
        },


        alertMessage: function() {
            alert(this.props.name);                             
        },

        render: function () {
            return (
                <div onClick={this.alertMessage}>
                Hello {this.props.name}
                </div>
            );
        }
    });

    React.render(<Hello name={'aworld'} />, document.body);

Espero que isso ajude um pouco, porque não sei se entendi sua pergunta corretamente, então me corrija se eu a interpretei errado :)

Danillo Felixdaal
fonte
2

método 1 using ChildRef :

public childRef: any = React.createRef<Hello>();

public onButtonClick= () => {
    console.log(this.childRef.current); // this will have your child reference
}

<Hello ref = { this.childRef }/>
<button onclick="onButtonClick()">Click me!</button>

Método 2: using window register

public onButtonClick= () => {
    console.log(window.yourRef); // this will have your child reference
}

<Hello ref = { (ref) => {window.yourRef = ref} }/>`
<button onclick="onButtonClick()">Click me!</button>
Mohideen bin Mohammed
fonte
o método 1 é uma maneira muito fácil e limpa de acessar métodos de componente filho. obrigado!
gabdara
2

Com React hook - useRef



const MyComponent = ({myRef}) => {
  const handleClick = () => alert('hello world')
  myRef.current.handleClick = handleClick
  return (<button onClick={handleClick}>Original Button</button>)
}

MyComponent.defaultProps = {
  myRef: {current: {}}
}

const MyParentComponent = () => {
  const myRef = React.useRef({})
  return (
    <>
      <MyComponent 
        myRef={myRef}
      />
      <button onClick={myRef.curent.handleClick}>
        Additional Button
      </button>
    </>
  )
}

Boa sorte...

Aakash
fonte
1

Eu uso este método auxiliar para renderizar componentes e retornar uma instância do componente. Os métodos podem ser chamados nessa instância.

static async renderComponentAt(componentClass, props, parentElementId){
         let componentId = props.id;
        if(!componentId){
            throw Error('Component has no id property. Please include id:"...xyz..." to component properties.');
        }

        let parentElement = document.getElementById(parentElementId);

        return await new Promise((resolve, reject) => {
            props.ref = (component)=>{
                resolve(component);
            };
            let element = React.createElement(componentClass, props, null);
            ReactDOM.render(element, parentElement);
        });
    }
Stefan
fonte