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

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

时间:2022-10-04 14:13:15浏览次数:72  
标签:实战 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 编写的子组件其实是最能简洁明了的

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

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/16753678.html

相关文章

  • 自定义UI实战
    自定义UI实战自定义View的流程有些时候会觉得Android中提供的控件不能满足项目的要求,所以就会常常去自定义控件。自定义控件就不免会自定义属性。自定义属性大致需要三个......
  • [Typescript + React] Tips: Write your own 'PropsFrom' helper to extract props fr
    Typehelperschangethegamewhenitcomestotypesinyourcodebase.TheyhelpTypeScriptinfermorefromyourcode-andmakeyourtypesalotmorereadable.......
  • React组件复用的技巧
    复用是组件化开发体系的立命之本,可以说组件化的初衷就是为了复用性。但是组件化的复用方式也存在一定的问题,其中拆分粒度就是其中一个绕不开的话题,今天咱们就来讲一讲Rea......
  • React源码解读之任务调度
    前言简单说下为什么React选择函数式组件,主要是class组件比较冗余、生命周期函数写法不友好,骚写法多,functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的b......
  • react的useState源码分析
    前言简单说下为什么React选择函数式组件,主要是class组件比较冗余、生命周期函数写法不友好,骚写法多,functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的b......
  • dp----得到方案方法的技巧
    《题一》原题链接:https://atcoder.jp/contests/abc271/tasks/abc271_d翻译:问题陈述有N张卡片,每面写一个整数。卡片正面写着一个整数ai,背面写着一位整数bi。您可以选......
  • Python中优雅的字典技巧总结
    1.引言在日常工作中,大家都需要进行字典的相关操作,对于某些初学者,经常会写一堆繁琐的代码来实现某项简单的功能。本篇文章重点介绍一些在Python中关于字典的一些简单技巧,熟......
  • prometheus+grafana+node-exporter部署监控系统实战
    1、在grafana中添加prometheus数据源  2、添加dashboard       ......
  • 【Vue.js入门到实战教程】15-ES 2015 新特性一览
    ​技术栈选择前面我们介绍了从Laravel8开始,自带的前端UI脚手架扩展包Jetstream不再基于Vue作为默认的组件开发技术栈,以便降低后端新手入门前端组件开发的学习成本......
  • 【运维实战】3.FastDFS分布式的文件存储系统进阶API使用实践
    ​本章目录:0x00FastDFSAPI使用实践JavaPython0x01FastDFS基础命令与配置1.FastDFS客户端命令浅析2.FastDFS服务端配置浅析0x00FastDFSAPI使用实践Java描述:Fast......