从Vue2.0到React17——React中实现Vue指令

4,762 阅读6分钟

“这是我参与更文挑战的第2天,活动详情查看: 更文挑战

前言

Vue提供了一些列的指令,帮助我们快速开发组件,如最常用的v-modelv-showv-ifv-for,这些指令的功能在React中是如何提供的。

1、React中的v-model

在Vue中的v-model的作用是实现数据双向绑定。这里要特别注意,Vue和React都是单向数据流的,数据双向绑定和数据流是两个独立的概念。

因为在Vue中v-model只能在表单元素<input><textarea><select>和组件Components上使用,故分表单元素和组件两种使用场景来介绍React中的v-model

1.1 表单元素的v-model

在React中是用受控组件的概念来实现表单元素上的v-model

那什么是受控组件呢?我是这么理解的。在表单元素<input><textarea><select>中通常是自己维护数据,并根据用户输入来更新数据。假如我们用React的state来替换这个数据会怎样呢?例如:

import React from 'react';
export default class Input extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      value:'请输入内容',
    }
  }
  render() {
    return (
      <input type="text" value={this.state.value}/>
    );
  }
}

会发现Input输入框无法输入内容了,这是因为<input/>标签的value属性是可读写的,当我们在Input输入框输入内容时其实是在改变value的值。

这是因为this.state.value在React类组件中只能通过this.setState()来修改。那么在<input/>标签的value属性被this.state.value赋值后,在Input输入框输入内容时,js内部无法修改this.state.value,导致无法修改value的值,从而造成Input输入框无法输入内容。

相当于Input输入框被this.state.value这个state给控制了,React将这种类型的Input输入框称为受控组件

那如何恢复Input输入框的输入功能呢?这要借助React的合成事件onChange来监听Input输入框的输入,获取输入值,再用this.setState()来把输入值赋值给this.state.value,间接来改变value的值。

import React from 'react';
export default class Input extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '请输入内容',
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    this.setState({ value: e.target.value });
  }
  render() {
    return (
      <>
        <input
          type="text"
          value={this.state.value}
          onChange={this.handleChange}
        />
        <span>{this.state.value}</span>
      </>
    );
  }
}

this.state.value和Input输入框绑定在一起,随着Input输入框输入内容的改变,this.state.value也会跟着改变,当this.state.value改变时,Input输入框的内容也会改变,这就是数据双向绑定。

以上实现的是不是和v-modle的功能一模一样。下面来看一下函数组件如何实现v-modle

import { useState } from "react";
export default function Index() {
  const [value, setValue] = useState('请输入内容');
  const handleChange = (e) => {
    setValue(e.target.value)
  }
  return (
    <>
    <input type="text" value={value} onChange={handleChange} />
    <span>{value}</span>
    </>
  );
}

1.2 React组件的v-model

Vue组件的v-model是个语法糖,本质上是利用名为value的prop和名为input的事件。

例如在组件上使用v-model="info"时,其实是把info数据传递给value,当info数据改变时组件的value也跟着改变。

双向数据绑定要求组件的value改变时info数据也得跟着改变,可以在组件的value改变时执行this.$emit('input',data),触发名为 input 的事件,该事件绑定函数(data) => { this.info = data},其中datavalue改变后的值,执行后就实现了双向数据绑定。

那么在React中也可以按这个思路来实现v-model

import React from 'react';
export default class HelloWorld extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <>
        <div>{this.props.value}</div>
        <button
          onClick={this.props.onChange.bind(this, '子组件改变info的值')}
        >
          子组件改变info的值
        </button>
      </>
    );
  }
}
import React from 'react';
import HelloWorld from './HelloWorld';
export default class Input extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      info: '父组件改变info的值',
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(data) {
    this.setState({ info: data });
  }
  render() {
    return (
      <>
        <button
          onClick={() =>{this.setState({info:'父组件改变info的值'})}}
        >
          父组件改变info的值
        </button>
        <HelloWorld
          value={this.state.info}
          onChange={this.handleChange}
        >
        </HelloWorld>
      </>
    );
  }
}

函数组件的写法:

export default function HelloWorld(props) {
  const { value, onChange } = props
  return (
    <>
      <div>{value}</div>
      <button
        onClick={() => { onChange('子组件改变info的值') }}
      >
        子组件改变info的值
       </button>
    </>
  );
}
import { useState } from 'react';
import HelloWorld from './HelloWorld';
export default function Input() {
  const [info, setInfo] = useState('父组件改变info的值');
  const handleChange = (data) => {
    setInfo(data)
  }
  return (
    <>
      <button
        onClick={() => { setInfo('父组件改变info的值') }}
      >
        父组件改变info的值
        </button>
      <HelloWorld
        value={info}
        onChange={handleChange}
      >
      </HelloWorld>
    </>
  );
}

2、React中的v-show

Vue的v-show本质是设置所添加指令的元素的css属性display的值,当v-show="true"时,把display设为block,当v-show='false'设为none

React中这样实现v-show,分类组件和函数组件来介绍。

类组件的写法:

import React from 'react';
export default class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      show: true
    }
  }
  render() {
    return (
      <div 
        style={{ 'display': this.state.show ? 'block' : 'none' }}
      >
        hello world
      </div>
    );
  }
}

函数组件的写法:

import { useState } from "react";

export default function Index() {
  const [show, setShow] = useState(false);
  return (
    <div
      style={{ 'display': show ? 'block' : 'none' }}
    >
      hello world
    </div>
  )
}

React中实现的v-show比Vue中更灵活,假如元素的css属性display的值为flex,使用v-show会导致样式错乱,而在React可以这样解决。

<div
  style={{ 'display': show ? 'flex' : 'none' }}
>
  hello world
</div>

3、React中的v-if和v-else

Vue中用v-ifv-else来控制元素是否被渲染。

在JSX语法中,JavaScript 表达式可以被包裹在{}中作为子元素,另外函数也可以被包裹在{}中作为子元素,该函数执行后必须返回React元素。同时在JSX语法中falsenullundefinedtrue 是合法的子元素,但它们并不会被渲染,故可以依据此特性来决定是否要渲染其他的 React 元素,来实现React中的v-ifv-else

类组件的写法:

  • 用JavaScript表达式包裹在{}中来实现:
import React from 'react';
export default class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      show: false
    }
  }
  render() {
    return (
      <React.Fragment>
        {this.state.show ? <div>hello world</div> : <div>hello React</div>}
      </React.Fragment>
    );
  }
}

React组件返回的元素和Vue一样必须有个根元素,所以用<React.Fragment>来包裹,<React.Fragment>不会在DOM中渲染出额外的元素,跟Vue中的<template>元素一样。

  • 用函数包裹在{}中来实现:
import React from 'react';
export default class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      show: false
    }
  }
  render() {
    return (
      <React.Fragment>
        {
          (() => {
            if (this.state.show) {
              return (
                <div>hello world</div>
              )
            } else {
              return (
                <div>hello world</div>
              )
            }
          })()
        }
      </React.Fragment>
    );
  }
}

函数组件的写法:

  • 用JavaScript表达式包裹在{}中来实现:
 import { useState } from "react";

export default function Index() {
  const [show, setShow] = useState(false);
  return (
    <>
      {show ? <div>hello world</div> : <div>hello React</div>}
    </>
  )
}

其中<></>相当<React.Fragment></React.Fragment>

  • 用函数包裹在{}中来实现:
import { useState } from "react";

export default function Index() {
  const [show, setShow] = useState(false);
  const Title = () => {
    if (show) {
      return (
        <div>hello world</div>
      )
    } else {
      return (
        <div>hello React</div>
      )
    }
  }
  return (
    <>
      {Title()}
    </>
  )
}

4、React中的v-for

在Vue中用v-for来渲染列表形式的组件,在Reacr中可以用map()函数来实现,但是要注意在map()方法中的元素需要设置key属性,否值会引起警告错误。

类组件的写法:

import React from 'react';
export default class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: ['小明', '小红', '小东']
    }
  }
  render() {
    return (
      <React.Fragment>
        {this.state.list.map((item, index) => {
          return (
            <div key={index}>{item}</div>
          )
        })}
      </React.Fragment>
    );
  }
}

当数组类型的数据中每一项没有唯一性id的时候,可以使用索引index作为key,如果数据的顺序可能会变化,不要使用索引index作为key,因为这样做会导致性能变差,还可能引起组件状态的问题,此时可以要求服务端给每项数据添加一个唯一值key,或者用随机数作为key

函数组件的写法:

import { useState } from "react";

export default function Index() {
  const [list, setList] = useState(['小明', '小红', '小东']);
  return (
    <>
      {list.map((item, index) => {
          return (
            <div key={index}>{item}</div>
          )
        })}
    </>
  )
}

小结

本文介绍了Vue开发中常用的指令在React中是如何实现的。读完本文,你应该会按原先用Vue开发一些复杂组件的UI界面及UI交互的思路去用React来开发一些复杂组件的UI界面及UI交互。然而组件的业务交互一般是在组件的生命周期钩子函数中去处理的,比如从服务端请求数据等等。所以下一篇文章将介绍React组件的生命周期。