Vue vs React - 如何从一个框架到另一个框架

1,276 阅读23分钟

这些天来,每隔一段时间就会发布一个新的趋势性前端框架。但React和Vue.js仍然是所有其他替代方案中最受欢迎的。

尽管这两个框架都是高性能的,优雅的,而且可以说是容易学习的,但它们对某些事情应该如何做有一些不同的看法,而且有不同的方式来实现相同的最终结果。

我相信使用一个前端框架的舒适性和效率主要是关于学习做常规事情的模式。

你知道,如何监听一个参数/数据的变化,并对其执行一些动作。以及如何将事件监听器或数据绑定到一个动作对象(按钮、复选框等)等等。

当我在用React做一个副业的时候,我注意到我的想法是这样的。"是的,我可以在Vue中这样做,我将从孩子那里发出一个事件,然后在父方监听它,并更新这个数据"。然后我就在谷歌上搜索如何在React中做这样的事情。

在这篇文章中,我将向你展示如何在React和Vue中应用一些你在日常前端工作中会遇到的常见模式。然后你可以使用这些配方来轻松地从一个框架过渡到另一个框架。

无论你是一个有经验的Vue开发者,需要在React项目上工作,还是相反,这都会有帮助。我将使用现代React的钩子和Vue选项API(Vue 2)。

我建议克隆包含我在本文中使用的所有代码的仓库,并渲染每个部分中的各自组件,玩玩它们,以真正了解它们的工作原理。

克隆后,你需要在React和Vue的文件夹中运行npm install。然后你可以用npm start启动React项目,用npm run serve启动Vue项目。

https://github.com/yigiterinc/VueVsReact

GitHub链接

目录

组件结构

让我们鸟瞰一下两个框架中的一些非常基本的组件。我们将在下面的章节中对此进行扩展。

在Vue中,一个单文件组件包含三个部分:模板、脚本和样式。

模板是将被渲染的部分。它包含组件的HTML,并且可以访问脚本和样式中的数据(和方法)。

你可以在Vue的这些部分找到关于一个组件的一切。

<template>
  <div id="structure">
    <h1>Hello from Vue</h1>
  </div>
</template>

<script>
export default {}
</script>

<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#structure {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

为了让这个组件在没有路由器或其他复杂东西的情况下被渲染,你可以把它添加到App.vue中。 我建议你在跟随过程中渲染每一个组件,这样你就可以看到它们的运行情况。

<template>
  <div id="app">
    <structure />
  </div>
</template>

<script>
import Structure from './Structure/Structure.vue'

export default {
  name: 'App',
  components: { Structure },
}
</script>

App.vue

你将改变组件部分的导入组件和模板部分的标签名称。

在React中,功能组件是一个返回JSX的函数(JavaScript的一个扩展,允许你在JS代码中使用HTML标签)。你可以把它想成是返回HTML来简化事情。如果你对基于类的React比较熟悉的话,将被渲染的部分过去是写在render() 函数里面的。

随着你在本教程中的进展,在每个部分,你可以把各自的组件放在App.js中,让它们像这样被渲染。

import React from 'react'

function Structure() {
  return <div>Render me App</div>
}

export default Structure

组件

import './App.css'

import Structure from './Structure/Structure'

function App() {
  return (
    <div className="App">
      <Structure />
    </div>
  )
}

export default App

App.js

所以你将改变导入和div里面的组件。

如何使用状态

在Vue中,我们了解到脚本标签包含与组件相关的数据和方法。Vue选项API有一些特殊的关键词(options),如_数据、方法、道具、计算、观察_ 和_混合器_,我们可以使用,还有生命周期方法,如_创建_和_装载_。

我们将使用data 选项,在我们的组件中使用状态。数据应该被定义为一个函数,返回一个包含我们状态的对象。

为了访问我们HTML(模板)里面的状态,我们必须使用双大括号,并写上我们的变量名称。请记住,如果数据变量在HTML中被使用(引用),对该变量的任何改变都会导致渲染。

<template>
  <div>
    <h1>Hello {{ currentFramework }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentFramework: ' Vue!',
      alternative: ' React!',
    }
  },
}
</script>

<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style></style>

UsingState.vue

在React中,功能组件曾经是无状态的。但由于有了钩子,我们现在有了useState 钩子来在我们的组件内存储状态。要使用useState钩子,我们必须导入它,其语法是。

import React, { useState } from 'react';


function App() {
    const [stateName, setStateName] = useState('default value'); 
}

useState 语法

我们在括号内定义状态变量的名称和它的setter函数的名称,然后我们把我们的变量的默认值传递给useState钩子。

你可以这样想象这个钩子,以便更好地理解这个语法。它就像一个函数,创建一个变量,将其值设置为传递的值,然后返回一个包含该变量和其设置函数的数组。

注意,你应该使用一对大括号来切换到JavaScript范围,并在你的JSX中打印一个变量,而不是使用双括号,Vue就是这样。

import { React, useState } from 'react'

function TestUseState() {
  const [frameworkName, setFrameworkName] = useState('React')

  return (
    <div>
      <h1>useState API</h1>
      <p>Current Framework: {frameworkName}</p>
    </div>
  )
}

export default TestUseState

如何使用道具

在Vue中,我们通过在脚本字段内导出的对象中添加props选项来定义props,就像我们对数据选项所做的那样。最好的做法是将道具定义为对象,这样我们就能对它们的使用方式有更多控制。

例如,指定它们的类型、默认值,并在必要时让它们成为必需品。如果你错误地使用该组件,比如在没有传递必要的道具的情况下调用它,Vue会显示一个警告。

假设我们有一个Address子组件,它将被从UserInfo 父组件中调用。

<template>
  <div class="address">
    <p>City: {{ city }}</p>
    <p>Street: {{ street }}</p>
    <p>House No: {{ houseNumber }}</p>
    <p>Postal Code: {{ postalCode }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {}
  },
  props: {
    city: {
      type: String,
      default: 'Munich',
    },
    street: {
      type: String,
      required: true,
    },
    houseNumber: {
      type: Number,
      required: true,
    },
    postalCode: {
      type: Number,
      required: true,
    },
  },
}
</script>

<style></style>

子组件(Address.vue

我们可以像访问数据变量一样访问我们的props--使用模板内的双括号。而且我们可以像这样从父组件传递道具。

<template>
  <div class="address">
    <p>Name: Yigit</p>
    <Address
      street="randomStrasse"
      :postalCode="80999"
      :houseNumber="32"
    ></Address>
  </div>
</template>

<script>
import Address from '@/components/Address.vue'

export default {
  data() {
    return {}
  },
  components: {
    Address,
  },
}
</script>

<style></style>

父组件 (UserInfo.vue)

注意我们如何使用v-bind速记: ,并写上:postalCode:houseNumber ,以表明这些不是字符串,而是Number类型的对象。只要我们需要传递字符串以外的东西(数组、对象、数字等等),我们就必须使用这种语法。

如果你是从React来的,这可能会让你感到困惑,所以你可能想阅读更多关于v-bind的内容,以更好地了解它是如何工作的。

在React中,我们不需要明确定义哪些道具将被传递到子组件中。我们可以使用对象析构将道具分配给变量,或者使用道具对象访问它们。我们在JSX里面访问我们的props,就像我们访问状态一样。

import React from 'react'

function Address({ city, street, postalCode, houseNumber }) {
  return (
    <div>
      <p>City: {city}</p>
      <p>Street: {street}</p>
      <p>Postal Code: {postalCode}</p>
      <p>House Number: {houseNumber}</p>
    </div>
  )
}

export default Address

子组件 (Address.js)

而我们可以像这样从父组件中传递props。

import React from 'react'

function UserInfo() {
  return (
    <div>
      <p>Name: Yigit</p>
      <Address
        city="Istanbul"
        street="Ataturk Cad."
        postalCode="34840"
        houseNumber="92"
      ></Address>
    </div>
  )
}

export default UserInfo

父组件 (UserInfo.vue)

如何创建方法/函数

在Vue中,我们定义方法与数据类似--我们可以在数据下面放一个方法选项,然后定义方法。我们可以从模板中调用这些方法,方法可以访问/修改我们的数据。

<template>
  <div>
    {{ sayHello() }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      to: 'Methods',
    }
  },
  methods: {
    sayHello() {
      return 'Hello ' + this.to
    },
  },
}
</script>

<style></style>

sayHello.vue

当试图从导出的对象内部(脚本标签内的代码)访问组件方法或数据属性时要小心。如果你不包括this 关键字,Vue会显示一个错误,说它不知道那个属性/方法在哪里。

在React中,事情就比较简单了。它只是常规的JS函数定义,如果你愿意的话,可以使用ES6语法。

import React from 'react'

function HelloFunctions() {
  const to = 'Functions'

  function sayHello() {
    return 'Hello ' + to
  }

  const sayHelloModern = () => 'Hello ' + to

  return (
    <div>
      {sayHello()}
      <br />
      {sayHelloModern()}
    </div>
  )
}

export default HelloFunctions

SayHello.js

风格化选项

Vue组件的样式设计真的很简单。我们只需要在style 标签中写上我们普通的CSS类和选择器。

Vue还支持通过使用scoped 关键字对CSS进行范围控制。它有助于避免在不同的组件中分配相同的类名而引起的视觉错误。例如,你可以在你所有的组件中把主容器命名为main-container ,只有该组件文件中的样式才会应用于每个主容器。

<template>
  <div class="main-container">
    <h3 class="label">I am a styled label</h3>
  </div>
</template>

<script>
export default {
  data() {
    return {}
  },
}
</script>

<style scoped>
.main-container {
  position: absolute;
  left: 50%;
  top: 45%;
  margin: 0;
  transform: translate(-50%, -50%);
  text-align: center;
}

.label {
  font-size: 30px;
  font-weight: 300;
  letter-spacing: 0.5rem;
  text-transform: uppercase;
}
</style>

Styling.vue

在React中,我们在样式设计方面有更多的选择,而且基本上取决于你的个人喜好,因为有多种方法来设计你的组件。我将在这里建议几个好的选择。

1) 在.css文件中编写常规的CSS,并将其导入

这可能是为你的React组件应用样式的最基本和最直接的方法。这并不意味着它是一个坏方法,因为它允许你编写普通的旧CSS。如果你是一个刚开始使用React的CSS大师,这是一个好方法。

import React from 'react'
import './styles.css'

function Styled() {
  return (
    <div>
      <h3 class="title">I am red</h3>
    </div>
  )
}

export default Styled

Styled.js

.title {
  color: red;
  font-size: 30px;
}

styles.css

2) 使用Material UI (useStyles/makeStyles)

Material UI是一个CSS框架,有很多可重用的组件。它还提供了一种为你的组件设计样式的方法,它在JS中使用CSS,因此有它的优势,如范围性CSS。

makeStyles 钩子接收一个对象中的类列表,然后你可以通过将这些类分配给对象来使用这些类。

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    border: 0,
    borderRadius: 3,
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
    color: 'white',
    height: 48,
    padding: '0 30px',
  },
});

export default function Hook() {
  const classes = useStyles();
  return <Button className={classes.root}>Hook</Button>;
}

3) 使用风格化组件(JS中的CSS

样式化组件是现代的、易于使用的,它使你也可以利用普通CSS的所有功能。

在我看来,它比MaterialUI更容易使用,也更强大(你也可以用它来样式MaterialUI组件,而不是使用makeStyles )。它也比导入一个CSS文件要好,因为它是有范围的,而且样式化的组件是可重复使用的。

import React from 'react'
import styled, { css } from 'styled-components'

// Use Title and Wrapper like any other React component – except they're styled!
const Title = styled.h1`
  font-size: 2em;
  text-align: center;
  color: palevioletred;
`

// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
  height: 100vh;
`

function StyledComponent() {
  return (
    <Wrapper>
      <Title>Hello World!</Title>
    </Wrapper>
  )
}

export default StyledComponent

样式化组件

如何将表单输入绑定到数据(状态)上

我们已经学会了如何在我们的组件中设置状态,但我们还需要一种方法将用户输入与该状态绑定。例如在登录表单中,我们可能需要将用户的用户名和密码输入存储在组件状态中。React和Vue有不同的方式来保持用户输入与状态的同步。

在Vue中,我们有一个特殊的指令来实现这个操作,叫做v-model。要使用这个,你需要通过使用data 属性来创建一个状态,就像我们之前学到的那样。然后你在你的输入中添加v-model关键字,并指定哪个数据变量负责存储这个输入(这适用于表单输入、文本区域和选择元素)。

这是一种高层次的、干净的连接数据的方式,不需要创建额外的lambda函数或处理程序。

<template>
  <div>
    <input v-model="inputState" type="text" />
    <br />
    {{ inputState }}
    <br />
    <button @click="changeInputState()">Click to say goodbye</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      inputState: 'Hello',
    }
  },
  methods: {
    changeInputState: function () {
      this.inputState = 'Goodbye'
    },
  },
}
</script>

<style></style>

这里有一个小例子:我们有一个文本输入,我们通过使用v-model关键字将其与inputState 。因此,每当用户输入文本时,inputState 变量就会自动反映变化。

然而,有一件特别的事情你需要知道:v-model实现了2-way数据绑定,与React的1-way绑定不同。

这意味着,不仅当你改变输入时,数据会改变,而且,如果你改变数据,输入值也会改变。

为了证明这一点,我创建了一个按钮并将其连接到一个方法上。先不要担心事件的处理,我们将在下一节看到。当按钮被点击时,inputState变量的值被改变,输入也随之改变。

我鼓励你通过运行代码自己尝试一下。另外,注意你的输入框的初始值是'Hello'--它没有被初始化为空字符串或空,因为我们把inputState 变量设置为'Hello'。

现在让我们在React中看看。

import { React, useState } from 'react'

function FormInputBinding() {
  const [userInput, setUserInput] = useState('Hello')

  return (
    <div>
      <input type="text" onChange={(e) => setUserInput(e.target.value)} />
      <button onClick={() => setUserInput('Goodbye')}>
        Click to say goodbye
      </button>
      {userInput}
    </div>
  )
}

export default FormInputBinding

这个话题与处理用户事件重叠,所以如果你不明白的地方,等你完成下一节。在这里,我们手动处理onChange 事件,并调用setUserInput 函数,将状态设置为事件的值。

正如我们前面提到的,React使用了一个单程绑定模型。这意味着改变userInput的状态不会影响我们在文本输入里面看到的值--我们最初不会在输入框里面看到Hello。此外,当我们点击按钮时,它将更新状态,但输入框内的输入将保持其值。

处理事件(用户输入

让我们看看另一个更接近于Vue和React的真实案例的表单。

<template>
  <div>
    <input
      v-model="username"
      id="outlined-basic"
      label="Username"
      variant="outlined"
    />
    <input
      v-model="password"
      id="outlined-basic"
      type="password"
      label="Password"
      variant="outlined"
    />

    <input
      v-model="termsAccepted"
      id="outlined-basic"
      type="checkbox"
      label="Password"
      variant="outlined"
    />

    <Button variant="contained" color="primary" @click="submitForm">
      Submit
    </Button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: '',
      termsAccepted: false,
    }
  },
  methods: {
    submitForm: function () {
      console.log(this.username, this.password, this.termsAccepted)
    },
  },
}
</script>

<style></style>

Vue表单

正如你所看到的,我们正在使用我们刚刚学到的v-model属性来连接我们所有的输入和数据属性(状态)。因此,每当输入发生变化时,Vue会自动更新相应的变量。

要看我们如何处理按钮的点击事件,请看提交按钮。我们使用v-on关键字来处理点击事件。@click 只是v-on:click 的一个缩写。

每当点击事件发生时,它只是调用submitForm 方法。你可以通过浏览链接的文档来熟悉可能的事件列表。

在React中,我们可以有一个这样的表单。

import { React, useState } from 'react'

import {
  TextField,
  Checkbox,
  FormControlLabel,
  Button,
} from '@material-ui/core'

function EventHandling() {
  let [username, setUsername] = useState('')
  let [password, setPassword] = useState('')
  let [termsAccepted, setTermsAccepted] = useState(false)

  const submitForm = () => {
    console.log(username, password, termsAccepted)
  }

  const formContainer = {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    gap: '20px',
  }

  return (
    <div style={formContainer}>
      <TextField
        onInput={(e) => setUsername(e.target.value)}
        id="outlined-basic"
        label="Username"
        variant="outlined"
      />
      <TextField
        onInput={(e) => setPassword(e.target.value)}
        id="outlined-basic"
        type="password"
        label="Password"
        variant="outlined"
      />

      <FormControlLabel
        control={
          <Checkbox
            type="checkbox"
            checked={termsAccepted}
            onChange={(e) => setTermsAccepted(e.target.checked)}
          />
        }
        label="Accept terms and conditions"
      />

      <Button variant="contained" color="primary" onClick={() => submitForm()}>
        Submit
      </Button>
    </div>
  )
}

export default EventHandling

React 表单

我们为每个输入创建我们的状态变量。然后我们可以监听输入的事件,事件将在处理函数中被访问。我们调用状态设置器来更新我们的状态,作为对这些事件的回应。

有条件的样式设计

条件式样是指在一个条件为真时,将一个类或样式绑定到一个元素上。

在Vue中,它可以这样实现。

<template>
  <div>
    <button @click="toggleApplyStyles"></button>
    <p :class="{ textStyle: stylesApplied }">
      Click the button to {{ stylesApplied ? 'unstyle' : 'style' }} me
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      stylesApplied: false,
    }
  },
  methods: {
    toggleApplyStyles: function () {
      this.stylesApplied = !this.stylesApplied
    },
  },
}
</script>

<style>
.textStyle {
  font-size: 25px;
  color: red;
  letter-spacing: 120%;
}
</style>

Vue的条件式样

我们创建了一个段落,希望只有在stylesApplied 数据属性的值为真时才应用textStyle 类。我们可以使用v-bind来实现这一点。冒号是v-bind的缩写,所以:classv-bind:class 相同。

我们使用v-bind对象语法来绑定类。我们将一个对象传递给类属性。{textStyle: stylesApplied},这意味着如果stylesApplied 为真,就应用textStyle 类。

这在开始时有点复杂,但它帮助我们避免了多个连锁的if语句来决定哪个类将被应用,并以一种干净的方式处理样式绑定:类名在左边(对象键),条件在右边(对象值)。

在React中,我们必须更原始地做事情。

import { React, useState } from 'react'
import './styles.css'

function ConditionalStyling() {
  let [stylesApplied, setStylesApplied] = useState(false)

  return (
    <div>
      <button onClick={() => setStylesApplied(!stylesApplied)}>Click me</button>
      <p style={{ color: stylesApplied ? 'red' : 'green' }}>Red or Green</p>
      <p className={stylesApplied ? 'styleClass' : ''}>Red with class</p>
    </div>
  )
}

export default ConditionalStyling

React的条件式样

在这里,我们使用普通的JavaScript来绑定一个样式对象或一个元素的类名。我认为这使代码变得有点复杂,而且我对这种语法不是很喜欢。

条件性渲染

有时,我们想在某些操作--比如从API中获取数据--完成后再渲染一个组件。或者我们想在有错误时显示错误信息,在没有错误时显示成功信息。

对于这种情况,我们使用条件渲染来改变要以编程方式呈现的HTML。

在Vue中,也有专门的指令用于此。我们可以使用v-ifv-else ,甚至v-else-if ,根据条件渲染模板。

<template>
  <div>
    <h2 v-if="condition1">condition1 is true</h2>
    <h2 v-else-if="condition2">condition2 is true</h2>
    <h2 v-else>all conditions are false</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      condition1: false,
      condition2: false,
    }
  },
}
</script>

<style></style>

这种语法允许我们创建复杂的条件渲染链,而不需要用if else语句使我们的模板代码复杂化。

这里是用React完成相同输出的方法之一。

import React from 'react'

function ConditionalRendering() {
  const condition1 = false
  const condition2 = false

  function getMessage() {
    let message = ''

    if (condition1) {
      message = 'condition1 is true'
    } else if (condition2) {
      message = 'condition2 is true'
    } else {
      message = 'all conditions are false'
    }

    return <h1>{message}</h1>
  }

  return <>{getMessage()}</>
}

export default ConditionalRendering

这只是普通的JS和JSX,在这里没有魔法。

渲染数组(列表

现在让我们来看看我们如何渲染列表数据。在Vue中,有v-for 关键字来做这件事。names中的 语法name 意味着name变量将一直持有当前的名字。随着索引的变化,如果索引是0,它就是names[0] ,以此类推。我们也可以通过在括号内指定索引来访问它。v-for指令也需要一个键。

<template>
  <div>
    <h1>Names</h1>
    <ul>
      <li v-for="(person, index) in people" :key="index">{{ person.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      people: [
        {
          id: 0,
          name: 'Yigit',
        },
        {
          id: 1,
          name: 'Gulbike',
        },
        {
          id: 2,
          name: 'Mete',
        },
        {
          id: 3,
          name: 'Jason',
        },
        {
          id: 4,
          name: 'Matt',
        },
        {
          id: 5,
          name: 'Corey',
        },
      ],
    }
  },
}
</script>

请注意,我们也可以使用v-for指令来迭代一个对象的属性。记住,在JS中,数组只是带有数字键的对象的一个特殊子集。

在React中,我们将使用Arrays.map 函数来迭代数组,并为每个元素返回一个JXX标签。

import React from 'react'

function RenderingLists() {
  const cities = [
    'Istanbul',
    'München',
    'Los Angeles',
    'London',
    'San Francisco',
  ]

  return (
    <div>
      <h1>Cities</h1>
      {cities.map((city, index) => (
        <h4 key={index}>{city}</h4>
      ))}
    </div>
  )
}

export default RenderingLists

子级到父级的沟通

想象一下,你有一个表单组件和它里面的一个按钮子组件。你想在按钮被点击时执行一些动作,比如调用API提交数据--但你无法访问按钮组件中的表单数据,因为它存储在父组件中。你会怎么做呢?

好吧,如果你是在Vue中,你想从子节点发出一个自定义事件,并在父节点上采取行动。在React中,你会在父类中创建将被执行的函数(当按钮被点击时),该函数可以访问表单数据,并将其传递给按钮组件,这样它就可以调用父类的函数。

让我们看看Vue中的一个例子。

<template>
  <div>
    <button @click="buttonClicked">Submit</button>
  </div>
</template>

<script>
export default {
  methods: {
    buttonClicked: function () {
      this.$emit('buttonClicked') // Emits a buttonClicked event to parent
    },
  },
}
</script>

<style></style>

Child.vue

在我们的子组件中,我们有一个按钮,在点击时我们发出一个buttonClicked 事件。我们也可以在这个调用中发送数据。例如,如果这是一个文本输入框,而不是一个按钮,并且有自己的数据,我们可以用emit将数据发送到父组件。

在父组件中,我们需要监听我们刚刚创建的自定义buttonClicked 事件。

<template>
  <form action="#">
    <input v-model="username" type="text" />
    <child @buttonClicked="handleButtonClicked" />
  </form>
</template>

<script>
import Child from './Child.vue'

export default {
  components: { Child },
  data() {
    return {
      username: '',
    }
  },
  methods: {
    handleButtonClicked: function () {
      console.log(this.username)
    },
  },
}
</script>

<style></style>

Parent.vue

我们只是给我们的子组件调用添加了一个@buttonClicked 事件来处理这个自定义事件。

在React中,我们可以通过向子组件传递一个处理函数来实现同样的结果。这个概念一开始对我来说有点复杂,但语法比Vue的例子要简单,而且没有什么魔法。

import React from 'react'

function Child({ handleButtonClicked }) {
  return (
    <div>
      <button onClick={() => handleButtonClicked()}>Submit</button>
    </div>
  )
}

export default Child

Child.js

我们访问handleButtonClicked 道具,并在按钮被点击时调用它。

import { React, useState } from 'react'

import Child from './Child.js'

function Parent() {
  const [username, setUsername] = useState('')

  const submitForm = () => {
    console.log(username)
    // Post form data to api...
  }

  return (
    <div>
      <input onChange={(e) => setUsername(e.target.value)} type="text" />
      <Child handleButtonClicked={submitForm} />
    </div>
  )
}

export default Parent

在父类中,我们将submitForm 函数作为handleButtonClicked 道具传递,这将完成这项工作。

对数据/状态变化做出反应

在某些情况下,我们需要对数据变化做出反应。例如,当你想执行异步或昂贵的操作来响应数据的变化时。

在Vue中,我们有watch 属性或观察者 。如果你熟悉React中的useEffect ,这是你在Vue中能找到的最接近的东西,它们的使用情况也大致相同。

让我们看一个例子。

<template>
  <div>
    <input v-model="number1" type="number" name="number 1" />
    <input v-model="number2" type="number" name="number 2" />
    {{ sum }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      number1: 0,
      number2: 0,
      sum: 0,
    }
  },
  watch: {
    number1: function (val) {
      this.sum = parseInt(val) + parseInt(this.number2)
    },
    number2: function (val) {
      this.sum = parseInt(this.number1) + parseInt(val)
    },
  },
}
</script>

<style></style>

在这里,我们的数据属性中定义了number1number2 。我们有2个各自的输入,我们要打印这些数字的总和,我们希望当任何一个输入发生变化时,总和能够更新。

watch 属性中,我们写下我们想观察的变量的名称。在本例中,我们希望同时观察number1和number2。如果用户输入了一个输入,v-model 将改变相应的数据变量,当这种情况发生时,该变量的观察函数将被触发,sum的值将被重新计算。

请注意,在一个真实的应用中,你不需要为这个简单的事情使用watch,你只需要把sum放在computed 。这是一个捏造的例子,用来演示watch 是如何工作的。

在对更复杂的东西如对象、数组和嵌套结构使用watch 之前,我建议阅读这篇文章,因为你可能需要学习一些watch 选项,如deepimmediate

在React中,我们使用内置的useEffect 钩子来观察变化。

import { React, useState, useEffect } from 'react'

function ReactToDataChanges() {
  const [number1, setNumber1] = useState(0)
  const [number2, setNumber2] = useState(0)
  const [sum, setSum] = useState(0)

  useEffect(() => {
    console.log('I am here!')
    setSum(parseInt(number1) + parseInt(number2))
  }, [number1, number2])

  return (
    <div>
      <input
        onChange={(e) => setNumber1(e.target.value)}
        type="number"
        name="number 1"
      />
      <input
        onChange={(e) => setNumber2(e.target.value)}
        type="number"
        name="number 2"
      />
      {sum}
    </div>
  )
}

export default ReactToDataChanges

useEffect 希望在依赖关系发生变化时运行一个函数作为第一个参数,第二个参数是一个依赖关系列表。

请记住,这也是一个捏造的例子,用来演示useEffect (我们可以通过把sum变量从状态中取出来,在没有useEffect 的情况下实现同样的效果)。

我想展示这个钩子的一个非常常见的用例:在组件加载后从API获取数据。

useEffect(() => {
    fetchUserData()
}, [])

const fetchUserData = async () => {
    const url = '';
    const response = await axios.get(url);
    const user = response.data;
    setUser(user);
}

我们可以指定空数组,在组件渲染时运行一次useEffect 。在Vue中,我们会在一个生命周期钩子中做同样的操作,比如createdmounted

计算属性vs useMemo

Vue有一个叫做计算属性的概念,它的作用是缓存相当复杂的计算,并在某个依赖关系发生变化时重新评估其值(类似于watch )。

通过将逻辑转移到JavaScript,它在保持我们的模板干净和简洁方面也很有用。在这种情况下,它们就像普通的变量,我们不希望它们成为一种状态。

<template>
  <div>
    <h3>Yigit's Favorite Cities are:</h3>
    <p v-for="city in favCities" :key="city">{{ city }}</p>
    <h3>Yigit's Favorite Cities in US are:</h3>
    <p v-for="town in favCitiesInUS" :key="town">{{ town }}</p>
    <button @click="addBostonToFavCities">
      Why is Boston not in there? Click to add
    </button>
  </div>
</template>

<script>
export default {
  computed: {
    favCitiesInUS: function () {
      return this.favCities.filter((city) => this.usCities.includes(city))
    },
  },
  data() {
    return {
      favCities: [
        'Istanbul',
        'München',
        'Los Angeles',
        'Rome',
        'Florence',
        'London',
        'San Francisco',
      ],
      usCities: [
        'New York',
        'Los Angeles',
        'Chicago',
        'Houston',
        'Phoenix',
        'Arizona',
        'San Francisco',
        'Boston',
      ],
    }
  },
  methods: {
    addBostonToFavCities() {
      if (this.favCities.includes('Boston')) return
      
      this.favCities.push('Boston')
    },
  },
}
</script>

<style></style>

计算的例子

在这里,我们不想把favCitiesInUS 函数放在我们的模板里面,因为它的逻辑太多。

把它想象成一个函数,输出将被缓存。只有当favCitiesusCities (其依赖关系)发生变化时,该函数才会被重新评估。要尝试这一点,你可以点击按钮,看看模板如何变化。请记住,计算函数不接收任何参数。

我们可以使用useMemo 钩子来实现React中的相同结果。我们将函数包裹在useMemo钩子中,并在第二个参数中提供依赖关系的列表。每当这些依赖关系之一发生变化,React就会再次运行该函数。

import { React, useMemo, useState } from 'react'

function UseMemoTest() {
  const [favCities, setFavCities] = useState([
      'Istanbul',
      'München',
      'Los Angeles',
      'Rome',
      'Florence',
      'London',
      'San Francisco',
    ]),
    [usCities, setUsCities] = useState([
      'New York',
      'Los Angeles',
      'Chicago',
      'Houston',
      'Phoenix',
      'Arizona',
      'San Francisco',
      'Boston',
    ])

  const favCitiesInUs = useMemo(() => {
    return favCities.filter((city) => usCities.includes(city))
  }, [favCities, usCities])

  return (
    <div>
      <h3>Yigit's Favorite Cities are:</h3>
      {favCities.map((city) => (
        <p key={city}>{city}</p>
      ))}
      <h3>Yigit's Favorite Cities in US are:</h3>
      {favCitiesInUs.map((town) => (
        <p key={town}>{town}</p>
      ))}
      <button onClick={() => setFavCities([...favCities, 'Boston'])}>
        Click me to add
      </button>
    </div>
  )
}

export default UseMemoTest

Vue槽与Render Props

我们有时想创建能在其中显示其他组件的通用组件,比如一个能在其中显示任何类型项目的网格组件。

为了这个目的,Vue有一个叫做槽的机制。槽背后的逻辑其实很简单:你在组件中打开一个槽,它应该接收另一个组件来渲染(我们称它为消费者,因为它消费提供的元素)。

在生产者中,你把消费者应该渲染的组件传到它的标签里--你可以把它们看成是填补你在消费者中打开的槽。第一个元素将在第一个槽中被渲染,第二个在第二个槽中,以此类推。

如果有一个以上的槽,你也必须设置名称。让我们看一个例子。

<template>
  <div>
    <h3>Component in slot 1:</h3>
    <slot name="slot1"></slot>
    <h3>Hello slot1</h3>
    <slot name="slot2"></slot>
    <h3>Component in slot 3:</h3>
    <slot name="slot3"></slot>
  </div>
</template>

<script>
export default {}
</script>

<style></style>

Consumer.vue

这里是我们的消费者组件。它可能正在创建一个布局或一些来自多个组件的组合。我们创建槽,并给它们不同的名字。

<template>
  <div>
    <consumer>
      <custom-button
        text="I am a button in slot 1"
        slot="slot1"
      ></custom-button>
      <h1 slot="slot2">I am in slot 2, yayyy</h1>
      <custom-button text="I am in slot 3" slot="slot3"></custom-button>
    </consumer>
  </div>
</template>

<script>
import CustomButton from './CustomButton.vue'
import Consumer from './Consumer.vue'

export default {
  components: {
    CustomButton,
    Consumer,
  },
}
</script>

<style></style>

Producer.vue

这里是生产者通过指定槽的名称作为属性将组件传递给消费者的槽。

如果你对这个感兴趣,这里是简单的CustomButton 组件。

<template>
  <button>{{ text }}</button>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      default: 'I am a button component',
    },
  },
}
</script>

<style></style>

你能在不运行代码的情况下猜出输出吗?这可能是一个很好的练习,以确保你理解槽。

在React中,这就简单多了。我认为槽使事情过于复杂了。由于React使用JSX,我们可以直接把要渲染的组件作为一个道具来传递。

import React from 'react'
import Child from './Child'

function Parent() {
  const compToBeRendered = (
    <div>
      <h1>Hello</h1>
      <button>Im button</button>
    </div>
  )

  return (
    <div>
      <Child compToBeRendered={compToBeRendered}></Child>
    </div>
  )
}

export default Parent

Parent.js

import React from 'react'

function Child({ compToBeRendered }) {
  return (
    <div>
      <h1>In child:</h1>
      {compToBeRendered}
    </div>
  )
}

export default Child

Child.js

最后的思考

在文章的最后一节,我想分享我对这些框架的两点看法。

正如我们通过文章所看到的,Vue通常有自己的做事方式,对大多数事情有不同的构造。在我看来,有时这使得它有点难以上手。

另一方面,React就像纯粹的JS,融合了JSX:没有太多的魔力,也没有太多的特殊关键字需要学习。

虽然它可能不是最适合初学者的框架,但我相信Vue提供的抽象/关键字(如v-for、v-if和Options API)允许你在更高的抽象层次上编写代码(想想添加一个简单的语句来迭代组件,而不是一个低层次的多行地图函数)。

这些功能也使你的Vue代码更加结构化和简洁,因为该框架是有主见的。所以它有自己的做事方式,如果你以这种方式做事,你最终会得到易于阅读和理解的代码。

另一方面,React对事情的看法不是很有主见,为开发者提供了很多自由,让他们自己决定项目的结构。

但这种自由是有代价的:如果你是一个不了解最佳实践的初学者,很容易出现混乱的代码和结构不良的项目。

在我看来,另一个重要的区别是:如果你要用React建立一个非基本的项目,你将需要使用大量的外部库来以正常的速度开发,这意味着你也需要学习这些东西的工作原理。

结语

谢谢你的阅读,我希望这个比较是有用的。如果你想联系、提问或进一步讨论,欢迎在LinkedIn上给我留言。


Yiğit Kemal Erinç

Yiğit Kemal Erinç

我是慕尼黑工业大学的一名硕士生,也是Visa的一名兼职SWE。我喜欢从事Java微服务和前端的工作。请随时联系我 :)


如果你读到这里,请发推特给作者,表示你的关心。鸣谢

FreeCodeCamp的开源课程已经帮助超过40,000人获得了开发者的工作。开始吧