首页 > 其他分享 >这可能是你需要的React实战技巧

这可能是你需要的React实战技巧

时间:2022-11-04 09:55:31浏览次数:91  
标签:实战 return 技巧 show React visible props 组件

一、父组件通过 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 编写的子组件其实是最能简洁明了的

二、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 没有发生变化则不需要重新计算。

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

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"))

image.png

上面例子演示了两种弹窗使用方式,分别是 通过 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 大体都是一个实现原理。

标签:实战,return,技巧,show,React,visible,props,组件
From: https://www.cnblogs.com/xiatianweidao/p/16856723.html

相关文章

  • React组件之间的通信方式总结(上)
    先来几个术语:官方我的说法对应代码ReactelementReact元素letelement=<span>A爆了</span>Component组件classAppextendsReact.Component{}无Ap......
  • 年前端react面试打怪升级之路
    react和vue的区别相同点:数据驱动页面,提供响应式的试图组件都有virtualDOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范数据流动单......
  • 百度前端react面试题总结
    componentWillReceiveProps调用时机已经被废弃掉当props改变的时候才调用,子组件第二次接收到props的时候在调用setState之后发生了什么状态合并,触发调和:setState......
  • React组件之间的通信方式总结(下)
    一、写一个时钟用react写一个每秒都可以更新一次的时钟importReactfrom'react'importReactDOMfrom'react-dom'functiontick(){letele=<h1>{ne......
  • 《ASP.NET Core技术内幕与项目实战》精简集-高级组件4.3:请求数据校验
    本节内容,涉及到8.3(P269-P272),以WebApi说明为主。主要NuGet包:内置命名空间:System.ComponentModel.DataAnnotationsFluentValidation.AspNetCore(数据检验框架)  一、请......
  • React组件基础
    1.组件基本介绍组件是React中最基本的内容,使用React就是在使用组件组件表示页面中的部分功能多个组件可以实现完整的页面功能组件特点:可复用,独立,可组合2.Re......
  • Mac下开发技巧
    Mac下Vim编辑快捷键小结(移动光标)移动到行尾"$",移动到行首"0"(数字),移动到行首第一个字符处"^"移动到段首"{",移动到段尾"}"移动到下一个词"w",移动到上一个词"b"移动到文档......
  • Git实战
    分四步走:1.进入你要管理的文件夹2.初始化3.管理当前文件下的文件4.生成版本信息 gitinit ---初始化  gitstatus ---检测当前文件夹下面文件的状......
  • ScreenToGIF小技巧之降低GIF体积
    ScreenToGIF小技巧之保存前言今天尝试用ScreenToGIF截一个GIF,但是截出来体积有17M。而我在​​GIF截图工具推荐——screenToGIF​​中截图的时候文件才600kb。于是我决定一......
  • React 函数组件
    React函数组件1、定义方式React函数组件是指使用函数方法定义的组件。定义方式:与函数的定义方式相同,需要将内容return出来,需要注意的是最外层只有一个标签或者使用......