一、父组件通过 Ref 调用子组件中的办法

这里同时演示应用函数组件类组件的父子组件如何编写

子组件

  • React.forwardRef
  • React.useImperativeHandle
  • public、private、protected
/** * 申明一个 function component 作为子组件 * 通过 forwardRef 接管父组件传递的 ref * 通过 useImperativeHandle 革新原 ref * 同时定义类型 IFnChildInstance 明确返回的 ref 的类型(非 typescript 不必思考这个) * 同时演示了组件的 props 应该写在哪里 */interface IFnChildInstance {  show: () => void}interface IFnChildProps {  testname: string}const CompFnChild = React.forwardRef<IFnChildInstance, IFnChildProps>(function (  props,  ref) {  const { testname } = props  React.useImperativeHandle(ref, () => ({    show: () => setVisible(true),  }))  const [visible, setVisible] = React.useState(false)  return (    <div>      <p>演示下state:{visible ? "显示" : "暗藏"}</p>      <p>演示下props:{testname}</p>      <div onClick={() => setVisible(false)}>暗藏</div>    </div>  )})/** * 申明一个 class component 作为子组件 * 通过 public 明确这就是咱们心愿父亲组件能调用的办法(public/private/protected) */interface IClassChildProps {  testname: string}interface IClassChildState {  visible: boolean}class CompClassChild extends React.Component<  IClassChildProps,  IClassChildState> {  constructor(props: IClassChildProps) {    super(props)    this.state = {      visible: false,    }  }  public show = () => {    this.setState({      visible: true,    })  }  private handleHide = () => {    this.setState({      visible: false,    })  }  render() {    const { visible } = this.state    const { testname } = this.props    return (      <div>        <p>演示下state:{visible ? "显示" : "暗藏"}</p>        <p>演示下props:{testname}</p>        <div onClick={this.handleHide}>暗藏</div>      </div>    )  }}

父组件

  • React.useRef
  • React.createRef
function CompFnParent() {  const RefFnChild = React.useRef<IFnChildInstance>(null)  const RefClassChild = React.useRef<CompClassChild>(null)  const myname = "tellyourmad"  return (    <>      <div onClick={() => RefFnChild.current?.show()}>        调用 CompFnChild 的办法      </div>      <CompFnChild ref={RefFnChild} testname={myname} />      <div onClick={() => RefClassChild.current?.show()}>        调用 CompClassChild 的办法      </div>      <CompClassChild ref={RefClassChild} testname={myname} />    </>  )}class CompClassParent extends React.Component {  private RefFnChild = React.createRef<IFnChildInstance>()  private RefClassChild = React.createRef<CompClassChild>()  componentDidMount() {    // TODO  }  render() {    const myname = "tellyourmad"    return (      <>        <div onClick={() => this.RefFnChild.current?.show()}>          调用 CompFnChild 的办法        </div>        <CompFnChild ref={this.RefFnChild} testname={myname} />        <div onClick={() => this.RefClassChild.current?.show()}>          调用 CompClassChild 的办法        </div>        <CompClassChild ref={this.RefClassChild} testname={myname} />      </>    )  }}

总结一下,其实应用 class 形式再配合上 typescript 编写的子组件其实是最能简洁明了的

参考React实战视频解说:进入学习

二、memoize 的利用

get(computed)

平时咱们有时候会用 get 对一些数据进行解决:

interface IMyTestProps {}interface IMyTestState {  firstname: string  lastname: string}class MyTest extends React.Component<IMyTestProps, IMyTestState> {  get name() {    const { firstname, lastname } = this.state    return firstname + lastname  }  render(){    return (      <div>我的名字是:{this.name}</div>    )  }}

然而在每次触发 render 的时候都须要从新计算 get name,如果这里逻辑非常复杂,那将会耗费大量性能。其实很多时候咱们只须要判断入参有没有发生变化即可判断是否须要从新计算。譬如例子中,如果 firstnamelastname 没有发生变化则不须要从新计算。

memoize

这里应用 memoize 包裹后:

import { memoize } from "lodash"interface IMyTestProps {}interface IMyTestState {  firstname: string  lastname: string}class MyTest extends React.Component<IMyTestProps, IMyTestState> {  get url() {    const { firstname, lastname } = this.state    return this.getUrl(firstname, lastname)  }  private getUrl = memoize(function (firstname, lastname) {    return firstname + lastname  })  render(){    return (      <div>我的名字是:{this.name}</div>    )  }}
  • 这里用的是 lodash 库外面的办法,有趣味能够本人去看源码
  • 这种优化形式其实跟 debounce、throttle 都是一个意思,都是依据肯定条件来判断是否应该节约本次计算

p.s. 这里只是演示了一个简略的 firstname + lastname 例子,理论是不须要思考优化的,因为自身 memoize 也是要执行比拟逻辑,当入参数非常复杂时,这样优化其实是得失相当的,所以具体情况要具体分析。

三、实现一个弹窗组件(toast/modal/dialog)

你能够看到不论 antd(react) 还是 element(vue) 中的弹窗组件都是渲染在 document.body 上的,而非以后组件所对应的 render 节点上

import { Modal } from "antd"class Test extends React.Component {  componentDidMount() {    Modal.info({      title: "通过api",      visible: true,    })  }  render() {    return (      <div>        <div>测是是的话i说的</div>        <Modal title="通过组件" visible={true} />      </div>    )  }}ReactDOM.render(<Test />, document.getElementById("root"))

下面例子演示了两种弹窗应用形式,别离是 通过 api 调用应用 react 组件,上面会一一举例如何实现:

通过 api 调用

  1. document.createElement 创立 dom
  2. document.body.appendChild 插入 dom
  3. ReactDOM.render 渲染组件
  4. 调用实例中的办法 或者 间接给实例传递
import React from "react"import ReactDOM from "react-dom"const show = function (props: IModalProps) {  const node = document.createElement("div")  document.body.appendChild(node)  // 顺便说下,其实不用在这里写款式,没意义的  // node.style.zIndex = "999";  const handleClose = function () {    /**     * 在 modal 敞开后会触发销毁     * 目前这里是 setState({visible: false}) 之后就立马触发销毁的     * 如果想 antd 那样还有隐没过渡成果(transition)的话,还得加个提早哦~     *     * p.s. 不销毁会导致性能等问题     */    ReactDOM.unmountComponentAtNode(node) // 卸载 react 组件    document.body.removeChild(node) // 删除 dom 节点  }  /**  ReactDOM.render<IModalProps, Modal>(    <Modal      {...props}      onClose={() => {        props.onClose && props.onClose()        handleClose()      }}    />,    node  ).show() // render 之后调用实例中的 show 办法  **/  // 因为在将来的 react 中,组件的渲染又可能是异步的,所以不倡议间接应用 render 返回的实例,应该用上面形式  ReactDOM.render<IModalProps, Modal>(    <Modal      {...props}      onClose={() => {        props.onClose && props.onClose()        handleClose()      }}    />,    node,    function(this: Modal) {      this.show() // 在 callback 之后调用实例的办法    }  )}interface IModalProps {  title: string  onClose?: () => void}interface IModalState {  visible: boolean}class Modal extends React.Component<IModalProps, IModalState> {  constructor(props: IModalProps) {    super(props)    this.state = {      visible: false,    }  }  // 裸露 show 办法  public show = () => {    this.setState({      visible: true,    })  }  private handleClose = () => {    this.setState({      visible: false,    })    this.props.onClose && this.props.onClose()  }  render() {    return (      <div        style={{          // 其余款式就应该不必演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.state.visible ? "visible" : "hidden",        }}      >        <div>{this.props.title}</div>        <span onClick={this.handleClose}>敞开</span>      </div>    )  }}function Test() {  return (    <div onClick={() => show({ title: "题目" })}>      <p>通过调用 show 办法即可显示弹窗</p>    </div>  )}

应用 react 组件

  • ReactDOM.createPortal 建设传送门
  • 通过 props 管制
interface IModalProps {  visible: boolean  title: string  onClose: () => void}class Modal extends React.Component<IModalProps> {  private el: HTMLDivElement  private readonly container = document.body  constructor(props: IModalProps) {    super(props)    this.el = document.createElement("div")  }  componentDidMount() {    this.container.appendChild(this.el)  }  componentWillUnmount() {    this.container.removeChild(this.el)  }  private handleClose = () => this.props.onClose()  render() {    return ReactDOM.createPortal(      <div        style={{          // 其余款式就应该不必演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.props.visible ? "visible" : "hidden",        }}      >        <div>{this.props.title}</div>        <span onClick={this.handleClose}>敞开</span>      </div>,      this.el    )  }}function Test() {  const [visible, setVisible] = React.useState(false)  return (    <div onClick={() => setVisible(true)}>      <p>应用 react 组件</p>      <Modal title="题目" visible={visible} onClose={() => setVisible(false)} />    </div>  )}

合体

咱们的冀望是这个 Modal 组件像 antd 的一样,既能通过 Modal.show() 形式应用,也是通过 <Modal/> 形式应用,这里就将下面两个例子进行合并

const show = function (props: Omit<IModalProps, "visible">) {  const node = document.createElement("div")  document.body.appendChild(node)  const handleClose = function () {    ReactDOM.unmountComponentAtNode(node)    document.body.removeChild(node)  }  return ReactDOM.render<IModalProps, Modal>(    <Modal      {...props}      onClose={() => {        props.onClose && props.onClose()        handleClose()      }}    />,    node  ).show()}interface IModalProps {  visible?: boolean  title: string  onClose?: () => void}interface IModalState {  visible: boolean}class Modal extends React.Component<IModalProps, IModalState> {  /**   * 将 show 办法放到 class component 的静态方法中   */  static show = show  private el: HTMLDivElement  private readonly container = document.body  constructor(props: IModalProps) {    super(props)    this.el = document.createElement("div")    this.state = {      visible: false,    }  }  componentDidMount() {    this.container.appendChild(this.el)  }  componentWillUnmount() {    this.container.removeChild(this.el)  }  get visible() {    /**     * props 优先级比 state 高     * 保障如果内部在管制弹窗状态,则依据内部的来     */    if (typeof this.props.visible !== "undefined") {      return this.props.visible    }    return this.state.visible  }  public show = () => {    this.setState({      visible: true,    })    /**     * return 以后实例,提供链式调用的操作形式     * 譬如: Modal.show().hide()     */    return this  }  public hide = () => {    this.setState({      visible: false,    })    return this  }  private handleClose = () => {    this.setState({      visible: false,    })    this.props.onClose && this.props.onClose()  }  render() {    return ReactDOM.createPortal(      <div        style={{          // 其余款式就应该不必演示了吧,就不写了          position: "absolute",          zIndex: 999,          left: 0,          right: 0,          top: 0,          bottom: 0,          visibility: this.visible ? "visible" : "hidden",        }}      >        <div>{this.props.title}</div>        <span onClick={this.handleClose}>敞开</span>      </div>,      this.el    )  }}function Test() {  const [visible, setVisible] = React.useState(false)  return (    <>      <div        onClick={() =>          Modal.show({            title: "题目",          })        }      >        <p>应用 api 调用</p>      </div>      <div onClick={() => setVisible(true)}>        <p>应用 react 组件</p>        <Modal          title="题目"          visible={visible}          onClose={() => setVisible(false)}        />      </div>    </>  )}

总结一下,最初的这个组件有多种实现形式,这里只是很简略的演示一下,关键点在你要把握 ReactDOM.renderReactDOM.createPortal 的应用,当你把握了这两者,诸如 Toast、Dialog、Dropdown 大体都是一个实现原理。