从父级调用子级方法

2020/09/24 23:21 · javascript ·  · 0评论

我有两个组成部分。

  1. 父组件
  2. 子组件

我试图从父级调用孩子的方法,我尝试过这种方法,但没有得到结果

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>
    );
  }
}

有没有一种方法可以从父级调用子级的方法?

注意:子组件和父组件位于两个不同的文件中

首先,让我表示,这通常不是在React领域中解决问题的方法。通常,您要做的是将功能传递给道具中的子代,并传递事件中的子代的通知(或更好的是:)dispatch

但是,如果必须在子组件上公开命令式方法,则可以使用refs请记住,这是一个逃生舱口,通常表示可以使用更好的设计。

以前,仅基于类的组件支持引用。随着React Hooks的出现,情况不再如此

使用挂钩和功能组件(>= react@16.8

const { forwardRef, useRef, useImperativeHandle } = React;

// We need to wrap component in `forwardRef` in order to gain
// access to the ref object that is assigned using the `ref` prop.
// This ref is passed as the second parameter to the function component.
const Child = forwardRef((props, ref) => {

  // The component instance will be extended
  // with whatever you return from the callback passed
  // as the second argument
  useImperativeHandle(ref, () => ({

    getAlert() {
      alert("getAlert from Child");
    }

  }));

  return <h1>Hi</h1>;
});

const Parent = () => {
  // In order to gain access to the child component instance,
  // you need to assign it to a `ref`, so we call `useRef()` to get one
  const childRef = useRef();

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.getAlert()}>Click</button>
    </div>
  );
};

ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

对于文档useImperativeHandle()在这里

useImperativeHandle自定义使用时暴露给父组件的实例值ref

使用类组件(>= react@16.4

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('getAlert from Child');
  }

  render() {
    return <h1>Hello</h1>;
  }
}

ReactDOM.render(<Parent />, document.getElementById('root'));
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

旧版API(<= react@16.3

出于历史目的,这是您在16.3之前的React版本中使用的基于回调的样式:

const { Component } = React;
const { render } = ReactDOM;

class Parent extends Component {
  render() {
    return (
      <div>
        <Child ref={instance => { this.child = instance; }} />
        <button onClick={() => { this.child.getAlert(); }}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('clicked');
  }

  render() {
    return (
      <h1>Hello</h1>
    );
  }
}


render(
  <Parent />,
  document.getElementById('app')
);
<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="app"></div>

您可以在此处使用其他模式:

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应该已经分配了。

useEffect的替代方法:

上级:

const [refresh, doRefresh] = useState(0);
<Button onClick={() => doRefresh(prev => prev + 1)} />
<Children refresh={refresh} />

儿童:

useEffect(() => {
    performRefresh(); //children function of interest
  }, [props.refresh]);

https://facebook.github.io/react/tips/expose-component-functions.html
有关更多答案,请参考此处
在React子组件上调用方法

通过查看“原因”组件的引用,您正在破坏封装并使您无法仔细检查组件的所有使用位置就无法重构该组件。因此,我们强烈建议将ref视为组件的私有属性,就像state一样。

通常,数据应通过道具传递到树下。有一些例外(例如,调用.focus()或触发一次不会真正“改变”状态的一次性动画),但是每当您公开称为“ set”的方法时,道具通常更好的选择。尝试使其内部输入组件担心其大小和外观,以便其祖先都不做。

我们可以通过其他方式使用refs-

我们将创建一个Parent元素,它将渲染一个<Child/>组件。如您所见,将要渲染的组件需要添加ref属性并为其提供名称。

然后,
triggerChildAlert位于父类中函数将访问此上下文的refs属性(当triggerChildAlert触发函数时,将访问子引用,并且它将具有子元素的所有功能)。

class Parent extends React.Component {
    triggerChildAlert(){
        this.refs.child.callChildMethod();
        // to get child parent returned  value-
        // this.value = this.refs.child.callChildMethod();
        // alert('Returned value- '+this.value);
    }

    render() {
        return (
            <div>
                {/* Note that you need to give a value to the ref parameter, in this case child*/}
                <Child ref="child" />
                <button onClick={this.triggerChildAlert}>Click</button>
            </div>
        );
    }
}  

现在,按照先前的理论设计,子组件将如下所示:

class Child extends React.Component {
    callChildMethod() {
        alert('Hello World');
        // to return some value
        // return this.state.someValue;
    }

    render() {
        return (
            <h1>Hello</h1>
        );
    }
}

这是源代码-

希望能为您服务!

如果仅因为您希望子项为其父项提供可重用的特征而这样做,则您可以考虑改为使用render-props

该技术实际上使结构颠倒了。Child现在包装了父母,所以我改名到AlertTrait下面。我保留Parent连续性的名称,尽管现在它实际上并不是父母。

// Use it like this:

  <AlertTrait renderComponent={Parent}/>


class AlertTrait extends Component {
  // You will need to bind this function, if it uses 'this'
  doAlert() {
    alert('clicked');
  }
  render() {
    return this.props.renderComponent({ doAlert: this.doAlert });
  }
}

class Parent extends Component {
  render() {
    return (
      <button onClick={this.props.doAlert}>Click</button>
    );
  }
}

在这种情况下,AlertTrait提供一个或多个特征,将其作为道具传递到道具中指定的任何组件renderComponent

家长会收到doAlert作为道具,可以在需要时调用它。

(为清楚起见,我renderComponent在上面的示例中调用了prop 。但是在上面链接的React docs中,他们只是调用了它render。)

Trait组件可以在其render函数中渲染父对象周围的内容,但不渲染父对象内部的任何内容。实际上,如果它向父级传递了另一个道具(例如renderChild,则它可以在父级内部渲染事物,然后父级可以在其render方法中使用它。

这与OP要求的有所不同,但是有些人可能会像我们一样最终来到这里(因为我们这样做了),因为他们想要创建可重用的特征,并认为子组件是实现此目标的好方法。

我对这里介绍的任何解决方案都不满意。实际上,有一个非常简单的解决方案,可以使用纯Javascript而不依赖基本的props对象之外的某些React功能来完成-它为您提供了沿任一方向进行通信的优势(父->子,子->父)。您需要将对象从父组件传递到子组件。我称为“双向参考”或简称biRef的对象。基本上,对象包含对父对象要公开的方法的引用。子组件将方法附加到父可以调用的对象。像这样:

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      someParentFunction: someParentFunction
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef.someChildFunction = someChildFunction;

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

此解决方案的另一个优点是,您可以在父子项中添加更多功能,而仅使用单个属性即可将它们从父项传递给子项。

对上面代码的改进是不将父函数和子函数直接添加到biRef对象,而是添加到子成员。父函数应添加到名为“ parent”的成员,而子函数应添加到称为“ child”的成员。

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.child.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      parent: {
          someParentFunction: someParentFunction
      }
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.parent.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef {
       child: {
            someChildFunction: someChildFunction
       }
   }

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

通过将父函数和子函数置于biRef对象的单独成员中,您将可以清楚地将两者分开,并轻松查看哪些函数属于父或子。如果两个子组件中都出现相同的功能,这也有助于防止子组件意外覆盖父函数。

最后一件事是,如果您注意到,父组件使用var创建biRef对象,而子组件则通过props对象访问它。可能不尝试在父级中定义biRef对象,并通过其自己的props参数从其父级访问它(在UI元素的层次结构中可能是这种情况)。这是有风险的,因为当孩子实际上可能属于祖父母时,它可能认为它正在父级上调用的函数属于父级。只要您知道它,这没有什么错。除非您有理由支持父/子关系之外的某些层次结构,否则最好在父组件中创建biRef。

我希望我不再重复上面的内容,但是如何传递一个回调道具来设置父函数呢?这有效并且非常容易。(添加的代码在////之间)

class Parent extends Component {
  ///// 
  getAlert = () => {} // initial value for getAlert

  setGetAlertMethod = (newMethod) => {
    this.getAlert = newMethod;
  }
  /////

  render() {
    return (
      <Child setGetAlertMethod={this.setGetAlertMethod}>
        <button onClick={this.getAlert}>Click</button>
      </Child>
      );
    }
  }



class Child extends Component {
  /////
  componentDidMount() {
    this.props.setGetAlertMethod(this.getAlert);
  }
  /////

  getAlert() => {
    alert('clicked');
  }

  render() {
    return (
      <h1 ref="hello">Hello</h1>
    );
  }
}

您可以通过这种方式轻松实现

脚步-

  1. 在父类的状态下创建一个布尔变量。当您要调用函数时,请对其进行更新。
  2. 创建一个prop变量并分配布尔变量。
  3. 从子组件使用props访问该变量,并通过使用if条件执行所需的方法。

    class Child extends Component {
       Method=()=>{
       --Your method body--
       }
       render() {
         return (
        //check whether the variable has been updated or not
          if(this.props.updateMethod){
            this.Method();
          }
         )
       }
    }
    
    class Parent extends Component {
    
    constructor(){
      this.state={
       callMethod:false
      }
    
    }
    render() {
       return (
    
         //update state according to your requirement
         this.setState({
            callMethod:true
         }}
         <Child updateMethod={this.state.callMethod}></Child>
        );
       }
    }

我正在使用useEffect钩子来克服所有这些麻烦的事情,所以现在我将变量传递给孩子,如下所示:

<ParentComponent>
 <ChildComponent arbitrary={value} />
</ParentComponent>
useEffect(() => callTheFunctionToBeCalled(value) , [value]);

这是我的演示:https : //stackblitz.com/edit/react-dgz1ee?file=styles.css

useEffect用来调用子组件的方法。我尝试过,Proxy and Setter_Getter但很遗憾,这useEffect似乎是从父级调用子级方法的更方便的方法。要使用Proxy and Setter_Getter它,似乎首先要克服一些细微之处,因为首先呈现的元素是通过ref.current return => <div/>的特异性成为objectLike的元素关于useEffect,您还可以利用此方法根据您要对孩子执行的操作来设置父母的状态。

在我提供的演示链接中,您将找到我完整的ReactJS代码以及内部的草稿,以便您欣赏我的解决方案的工作流程。

在这里,我只为您提供我的ReactJS代码段的相关代码。

import React, {
  Component,
  createRef,
  forwardRef,
  useState,
  useEffect
} from "react"; 

{...}

// Child component
// I am defining here a forwardRef's element to get the Child's methods from the parent
// through the ref's element.
let Child = forwardRef((props, ref) => {
  // I am fetching the parent's method here
  // that allows me to connect the parent and the child's components
  let { validateChildren } = props;
  // I am initializing the state of the children
  // good if we can even leverage on the functional children's state
  let initialState = {
    one: "hello world",
    two: () => {
      console.log("I am accessing child method from parent :].");
      return "child method achieve";
    }
  };
  // useState initialization
  const [componentState, setComponentState] = useState(initialState);
  // useEffect will allow me to communicate with the parent
  // through a lifecycle data flow
  useEffect(() => {
    ref.current = { componentState };
    validateChildren(ref.current.componentState.two);
  });

{...}

});

{...}

// Parent component
class App extends Component {
  // initialize the ref inside the constructor element
  constructor(props) {
    super(props);
    this.childRef = createRef();
  }

  // I am implementing a parent's method
  // in child useEffect's method
  validateChildren = childrenMethod => {
    // access children method from parent
    childrenMethod();
    // or signaling children is ready
    console.log("children active");
  };

{...}
render(){
       return (
          {
            // I am referencing the children
            // also I am implementing the parent logic connector's function
            // in the child, here => this.validateChildren's function
          }
          <Child ref={this.childRef} validateChildren={this.validateChildren} />
        </div>
       )
}

您可以进行继承反转(在此处查找:https : //medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e)。这样,您就可以访问要包装的组件的实例(因此您将可以访问其功能)

我认为最基本的方法调用方法是在子组件上设置请求。然后,一旦子级处理了请求,它将调用一个回调方法来重置请求。

重置机制对于能够多次发送相同的请求是必不可少的。

在父组件中

在父级的render方法中:

const { request } = this.state;
return (<Child request={request} onRequestHandled={()->resetRequest()}/>);

父级需要2种方法,以便在2个方向上与其子级进行通信。

sendRequest() {
  const request = { param: "value" };
  this.setState({ request });
}

resetRequest() {
  const request = null;
  this.setState({ request });
}

在子组件中

孩子会更新其内部状态,复制道具的请求。

constructor(props) {
  super(props);
  const { request } = props;
  this.state = { request };
}

static getDerivedStateFromProps(props, state) {
  const { request } = props;
  if (request !== state.request ) return { request };
  return null;
}

然后,最后它处理请求,并将重置发送给父级:

componentDidMount() {
  const { request } = this.state;
  // todo handle request.

  const { onRequestHandled } = this.props;
  if (onRequestHandled != null) onRequestHandled();
}

从父级触发子级功能的另一种方法是利用componentDidUpdate子级Component 中的功能。triggerChildFunc从父母到孩子传递了一个道具,最初是null单击按钮时,该值将更改为函数,并且Child会注意到该更改componentDidUpdate并调用其自己的内部函数。

由于prop triggerChildFunc更改为函数,因此我们还会获得对Parent的回调。如果父级不需要知道何时调用该函数,则该值triggerChildFunc可以例如从更改nulltrue

const { Component } = React;
const { render } = ReactDOM;

class Parent extends Component {
  state = {
    triggerFunc: null
  }

  render() {
    return (
      <div>
        <Child triggerChildFunc={this.state.triggerFunc} />
        <button onClick={() => {
          this.setState({ triggerFunc: () => alert('Callback in parent')})
        }}>Click
        </button>
      </div>
    );
  }
}

class Child extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.triggerChildFunc !== prevProps.triggerChildFunc) {
      this.onParentTrigger();
    }
  }

  onParentTrigger() {
    alert('parent triggered me');

    // Let's call the passed variable from parent if it's a function
    if (this.props.triggerChildFunc && {}.toString.call(this.props.triggerChildFunc) === '[object Function]') {
      this.props.triggerChildFunc();
    }
  }

  render() {
    return (
      <h1>Hello</h1>
    );
  }
}


render(
  <Parent />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>

CodePen:https://codepen.io/calsal/pen/NWPxbJv editors = 1010

我们对自定义的钩子感到满意useCounterKey它只是设置一个counterKey或从零开始计数的密钥。它返回的功能会重置键(即增量)。(我相信这是React中重置组件的最惯用的方法,只需按一下键即可。)

但是,此钩子也可以在您希望向客户端发送一次性消息以执行某项操作的任何情况下使用。例如,我们使用它来将子项中的控件集中在某个父事件上-它仅在密钥更新时自动聚焦。(如果需要更多道具,可以在重置钥匙之前进行设置,以便在事件发生时可以使用。)

这个方法有点学习曲线b / c,它不像典型的事件处理程序那样简单,但是它似乎是我们在React中发现的最惯用的方式(因为键已经以这种方式起作用)。Def希望就此方法提供反馈,但效果很好!

// Main helper hook:
export function useCounterKey() {
  const [key, setKey] = useState(0);
  return [key, () => setKey(prev => prev + 1)] as const;
}

用法示例:

// Sample 1 - normal React, just reset a control by changing Key on demand
function Sample1() {
  const [inputLineCounterKey, resetInputLine] = useCounterKey();

  return <>
    <InputLine key={inputLineCounterKey} />
    <button onClick={() => resetInputLine()} />
  <>;
}

// Second sample - anytime the counterKey is incremented, child calls focus() on the input
function Sample2() {
  const [amountFocusCounterKey, focusAmountInput] = useCounterKey();

  // ... call focusAmountInput in some hook or event handler as needed

  return <WorkoutAmountInput focusCounterKey={amountFocusCounterKey} />
}

function WorkoutAmountInput(props) {
  useEffect(() => {
    if (counterKey > 0) {
      // Don't focus initially
      focusAmount();
    }
  }, [counterKey]);

  // ...
}

counterKey概念版权归肯特·多德斯所有。)

这是个错误?需要注意的地方:我同意使用forwardRef,useRef,useImperativeHandle的rossipedia解决方案

在线上有一些错误信息,指出只能从React Class组件创建引用,但是如果您使用上面提到的钩子,则确实可以使用功能组件。注意,这些钩子仅在我将文件更改为在导出组件时不与withRouter()一起使用后才起作用。即从

export default withRouter(TableConfig);

代替成为

export default TableConfig;

事后看来,这样的组件并不需要withRouter(),但是通常不会伤害到它。我的用例是我创建了一个组件来创建一个Table以处理配置值的查看和编辑,而且我希望能够在单击“父窗体”的“重置”按钮时告诉此Child组件重置其状态值。直到我从包含子组件TableConfig的文件中删除了withRouter(),UseRef()才能正确获取ref或ref.current(保持为空)。

本文地址:http://javascript.askforanswer.com/congfujidiaoyongzijifangfa.html
文章标签: ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!