用React.js写法改写Vue写法的教程(二)

目录
文章目录隐藏
  1. slot 插槽,在 React 中没找到?
  2. Context, React 中的 provide/inject
  3. 没有了 v-model,但也不影响使用
  4. 没有了指令,也不用迷茫
  5. 结语

用 React.js 写法改写 Vue 写法的教程(一)

昨天我写了 用React.js 写法改写 Vue 写法的教程(一),今天这篇文章是这一系列的第二篇文章,喜欢的小伙伴可以拿去参考学习。

slot 插槽,在 React 中没找到?

在使用 Vue 的时候,插槽是一个特别常用的功能,通过定义插槽,可以在调用组件的时候将外部的内容传入到组件内部,显示到指定的位置。在 Vue 中,插槽分为默认插槽,具名插槽和作用域插槽。其实不仅仅 Vue,在 React 中其实也有类似插槽的功能,只是名字不叫做插槽,下面我将通过举例来说明。

默认插槽

现在项目需要开发一个卡片组件,如下图所示,卡片可以指定标题,然后卡片内容可以用户自定义,这时候对于卡片内容来说,就可以使用插槽来实现,下面我们就分别使用 Vue 和 React 来实现这个功能。

默认插槽

Vue 实现

1.首先实现一个 card 组件,如下代码所示

<template>
  <div class="card">
    <div class="card__title">
      <span>{{ title }}</span>
    </div>
    <div class="card__body">
      <slot></slot>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String,
      default: ''
    }
  }
}
</script>

可以看到上面我们使用了<slot></slot>,这个就是组件的默认插槽,在使用组件的时候,传入的内容将会被放到<slot></slot>所在位置

2.其次,在外部使用定义的 card 组件

<template>
  <div>
    <my-card>
      <div>我将被放在 card 组件的默认插槽里面</div>
    </my-card>
  </div>
</template>
<script>
import MyCard from '../components/card'
export default {
  components: {
    MyCard
  }
}
</script>

如上代码,就可以使用组件的默认插槽将外部的内容应用到组件里面指定的位置了。

React 实现

虽然在React里面没有插槽的概念,但是React里面也可以通过props.children拿到组件标签内部的子元素的,就像上面代码<my-card>标签内的子元素,通过这个我们也可以实现类似Vue默认插槽的功能,一起看看代码。

1.首先,使用 React 定义 Card 组件

import React from 'react'

export interface CardProps {
  title: string,
  children: React.ReactNode
}

export default function(props: CardProps) {

  return (
    <div className="card">
      <div className="card__title">
        <span>{props.title}</span>
      </div>
      <div className="card__body">
        {/**每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容 */}
        {props.children}
      </div>
    </div>
  );
}

2.然后,在外部使用 Card 组件

import React from 'react'
import Card from './components/Card'

export default function () {

  return (
    <div>
      <Card title="标题">
        <div>我将被放在 card 组件的 body 区域内容</div>
      </Card>
    </div>
  );
}

具名插槽

继续以上面的Card组件为例,假如我们现在需求发生了变化,组件的title也可以使用插槽,这时候对于Vue就可以使用具名插槽了,而React也是有办法实现的哦。

Vue 实现

Vue的具名插槽主要解决的是一个组件需要多个插槽的场景,其实现是为<slot>添加name属性来实现了。

1.我们就上面的需求对card组件进行修改

<template>
  <div class="card">
    <div class="card__title">
      <!--如果传入了 title,则使用 title 属性,否则使用具名插槽-->
      <span v-if="title">{{ title }}</span>
      <slot v-else name="title"></slot>
    </div>
    <div class="card__body">
      <!--对于内容区域依然使用默认插槽-->
      <slot></slot>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String,
      default: ''
    }
  }
}
</script>

2.card 组件修改完之后,我们再去调整一下使用 card 组件的地方

<template>
  <div>
    <my-card>
      <!--通过 v-slot:title 使用具名插槽-->
      <template v-slot:title>
        <span>这里是标题</span>
      </template>
      <div>我将被放在 card 组件的默认插槽里面</div>
    </my-card>
  </div>
</template>
<script>
import MyCard from '../components/card'
export default {
  components: {
    MyCard
  }
}
</script>

React 实现

React连插槽都没有, 更别提具名插槽了,但是没有不代表不能模拟出来。对于Reactprops,我们不仅仅可以传入普通的属性,还可以传入一个函数,这时候我们就可以在传入的这个函数里面返回JSX,从而就实现了具名插槽的功能。

1.对原有的 Card 组件进行修改

import React from 'react'

export interface CardProps {
  title?: string,
  // 加入了一个 renderTitle 属性,属性类型是 Function
  renderTitle?: Function,
  children: React.ReactNode
}

export default function(props: CardProps) {

  const {title, renderTitle} = props
  // 如果指定了 renderTtile,则使用 renderTitle,否则使用默认的 title
  let titleEl = renderTitle ? renderTitle() : <span>{title}</span>

  return (
    <div className="card">
      <div className="card__title">{titleEl}</div>
      <div className="card__body">
        {/**每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容 */}
        {props.children}
      </div>
    </div>
  );
}

2.这时候就可以在外部自定义title

import React from 'react'
import Card from './components/Card'

export default function () {
  return (
    <div>
      <Card  renderTitle={
        () => {
          return <span>我是自定义的标题</span>
        }
      }>
        <div>我将被放在 card 组件的 body 区域内容</div>
      </Card>
    </div>
  );
}

作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的,这个就是Vue提供作用域插槽的原因。我们继续使用上面的Card组件为例,现在我基于上面的卡片组件开发了一个人员信息卡片组件,用户直接使用人员信息卡片组件就可以将人员信息显示到界面中,但是在某些业务模块需要自定义人员信息显示方式,这时候我们就需要使用到作用域插槽了。

Vue 实现

1.实现用户信息卡片组件,里面使用了作用域插槽

<template>
  <custom-card title="人员信息卡片">
    <div class="content">
      <!--这里使用了作用域插槽,将 userInfo 传出去了-->
      <slot name="userInfo" :userInfo="userInfo">
        <!--如果没有使用插槽,则显示默认内容-->
        <span>姓名: {{ userInfo.name }}</span>
        <span>性别: {{ userInfo.sex }}</span>
        <span>年龄: {{ userInfo.age }}</span>
      </slot>
    </div>
  </custom-card>
</template>
<script>
import CustomCard from '../card'
export default {
  components: {
    CustomCard
  },
  data() {
    return {
      userInfo: {
        name: '码云笔记',
        sex: '男',
        age: 25
      }
    }
  }
}
</script>

2.在外部使用人员信息组件

<template>
  <div>
    <user-card>
      <template v-slot:userInfo="{ userInfo }">
        <div class="custom-user">
          <ul>
            <li>姓名: {{ userInfo.name }}</li>
            <li>年龄: {{ userInfo.age }}</li>
          </ul>
        </div>
      </template>
    </user-card>
  </div>
</template>
<script>
import UserCard from '../components/user-card'
export default {
  components: {
    UserCard
  }
}
</script>

React 实现

在具名插槽那一小节我们通过给组件传入了一个函数,然后在函数中返回JSX的方式来模拟了具名插槽,那么对于作用域插槽,我们依然可以使用函数的这种方式,而作用域插槽传递的参数我们可以使用给函数传参的方式来替代

1.实现人员信息卡片组件

import React, { useState } from 'react'

import Card from './Card'

interface UserCardProps {
  renderUserInfo?: Function
}

export interface UserInfo {
  name: string;
  age: number;
  sex: string;
}

export default function(props: UserCardProps) {
  const [userInfo] = useState<UserInfo>({
    name: "码云笔记",
    age: 25,
    sex: "男",
  });

  const content = props.renderUserInfo ? (
    props.renderUserInfo(userInfo)
  ) : (
    <div>
      <span>姓名: {userInfo.name}</span>
      <span>年龄: {userInfo.age}</span>
      <span>性别: {userInfo.sex}</span>
    </div>
  );

  return <Card title="人员信息">
    {content}
  </Card>
}

2.在外部使用人员信息卡片组件

import React from 'react'
import UserCard, { UserInfo } from "./components/UserCard";

export default function () {

  return (
    <div>
      <UserCard
        renderUserInfo={(userInfo: UserInfo) => {
          return (
            <ul>
              <li>姓名: {userInfo.name}</li>
            </ul>
          );
        }}
      ></UserCard>
    </div>
  );
}

Context, React 中的 provide/inject

通常我们在项目开发中,对于多组件之间的状态管理,在Vue中会使用到Vuex,在React中会使用到redux或者Mobx,但对于小项目来说,使用这些状态管理库就显得比较大材小用了,那么在不使用这些库的情况下,如何去完成数据管理呢?比如面试最常问的祖孙组件通信。在Vue中我们可以使用provide/inject,在React中我们可以使用Context

假设有这样一个场景,系统现在需要提供一个换肤功能,用户可以切换皮肤,现在我们分别使用VueReact来实现这个功能。

Vue 中的 provide/inject

Vue中我们可以使用provide/inject来实现跨多级组件进行传值,就以上面所说场景为例,我们使用provide/inject来实现以下。

首先,修改App.vue内容为以下内容

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      themeInfo: {
        theme: 'dark'
      }
    }
  },
  provide() {
    return {
      theme: this.themeInfo
    }
  }
}
</script>

然后在任意层级的子组件中像下面这样使用

<template>
  <div :class="`child-${theme.theme}`">
  </div>
</template>
<script>
export default {
  inject: ['theme']
}
</script>

这样就可以实现theme在所有子组件中进行共享了

React 中的 Context

Vue中我们使用provide/inject实现了组件跨层级传值功能,在React中也提供了类似的功能即Context,下面我们使用Context来实现相同的功能。

在项目src目录下新建context目录,添加MyContext.js文件,然后添加以下内容

import {createContext} from 'react'
// 定义 MyContext,指定默认的主题为`light`
export const MyContext = createContext({
  theme: 'light'
})

MyContext提供了一个Provider,通过Provider可以将theme共享到所有的子组件。现在我们在所有的组件的共同父组件比如App.js上面添加MyContext.Providertheme共享出去

import { MyContext } from '@/context/MyContext';

export default function() {
  
  const [theme, setTheme] = useState('dark')
  
  return (
    <MyContext.Provider
        value={{
          theme
        }}
      >
        <Children></Children>
     </MyContext.Provider>
    )
  }

然后这时候就可以直接在所有的子组件里面使用定义的主题theme

import React, { useContext } from 'react'
import { MyContext } from '@/context/MyContext';

export default function() {
   const {theme}  = useContext(MyContext)
   return <div className={`child-${theme}`}>
}

没有了 v-model,但也不影响使用

我们知道ReactVue都是单向数据流的,即数据的流向都是由外层向内层组件进行传递和更新的,比如下面这段React代码就是标准的单向数据流.

import React, { useState } from "react";

export default function(){
  const [name] = useState('mybj')
  return <input value={name}></input>
}

在 vue 中使用 v-model

如上代码,我们在通过通过value属性将外部的值传递给了input组件,这个就是一个简单的单向数据流。但是在使用Vue的时候,还有两个比较特殊的语法糖v-model.sync,这两个语法糖可以让Vue组件拥有双向数据绑定的能力,比如下面的代码:

<template>
   <input v-model="name"/>
</template>
<script>
  export default {
    data() {
      return {
        name:'码云笔记'
      }
    }
  }
</script>

通过v-model,当用户修改input的值的时候,外部的name的值也将同步被修改。但这是Vue的语法糖啊,React是不支持的,所以React应该怎么办呢?这时候再想想自定义v-modelv-model实际上是通过定义value属性同时监听input事件来实现的,比如这样:

<template>
  <div class="custom-input">
     <input :value="value" @input="$_handleChange"/>
  </div>
</template>
<script>
  export default {
    props:{
      value:{
        type: String,
        default: ''
      }
    },
    methods:{
      $_handleChange(e) {
        this.$emit('input', e.target.value)
      }
    }
  }
</script>

在 react 寻找 v-model 替代方案

同理,React虽然没有v-model语法糖,但是也可以通过传入属性然后监听事件来实现数据的双向绑定。

import React, { useState } from 'react'

export default function() {
  const [name, setName] = useState('mybj')

  const handleChange = (e) => {
    setName(e.target.value)
  }
  return <div>
    <input value={name} onChange={handleChange}></input>
  </div>
}

我刚开始使用react,感觉没有v-model就显得比较麻烦,不过麻烦归麻烦,代码改写也要写。就像上文代码一样,每一个表单元素都需要监听onChange事件,越发显得麻烦了,这时候就可以考虑将多个onChange事件合并成一个,比如像下面代码这样:

import React, { useState } from 'react'

export default function () {
  const [name, setName] = useState('码云笔记')
  const [sex, setSex] = useState('男')

  const handleChange = (e:any, method: Function) => {
    method(e.target.value)
  }
  return <div>
    <input value={name} onChange={(e) => handleChange(e, setName)}></input>
    <input value={sex} onChange={(e) => handleChange(e, setSex)}></input>
  </div>
}

没有了指令,也不用迷茫

Vue中我们一般绘制页面都会使用到templatetemplate里面提供了大量的指令帮助我们完成业务开发,但是在React中使用的是JSX,并没有指令,那么我们应该怎么做呢?下面我们就将Vue中最常用的一些指令转换为JSX里面的语法(注意: 在 Vue 中也可以使用JSX)

v-show 与 v-if

Vue中我们隐藏显示元素可以使用v-show或者v-if,当然这两者的使用场景是有所不同的,v-show是通过设置元素的display样式来显示隐藏元素的,而v-if隐藏元素是直接将元素从dom中移除掉。

1.看一下Vue中的v-showv-if的用法

<template>
  <div>
    <span v-show="showName">姓名:{{ name }}</span>
    <span v-if="showDept">{{ dept }}</span>
  </div>
</template>
<script>
export default {
  data() {
    return {
      name: '码云笔记',
      dept: 'mybj',
      showName: false,
      showDept: true
    }
  }
}
</script>

2.将v-showv-if转换为JSX中的语法

Vue中指令是为了在template方便动态操作数据而存在的,但是到了React中我们写的是JSX,可以直接使用JS,所以指令是不需要存在的,那么上面的v-show,v-if如何在JSX中替代呢?

import React, { useState } from 'react'

export default function() {
  const [showName] = useState(false)

  const [showDept] = useState(true)

  const [userInfo] = useState({
    name:'子君',
    dept: '银河帝国'
  })

  return (
    <div>
      {/**模拟 v-show */}
      <span style={{display: showName ? 'block' : 'none'}}>{userInfo.name}</span>
      {/**模拟 v-if */}
      {showDept ? <span>{userInfo.dept}</span>: undefined}
    </div>
  )
}

v-for

v-forVue中是用来遍历数据的,同时我们在使用v-for的时候需要给元素指定keykey的值一般是数据的id或者其他唯一且固定的值。不仅在Vue中,在React中也是存在key的,两者的key存在的意义基本一致,都是为了优化虚拟DOM diff算法而存在的。

1.在Vue中使用v-for

<template>
  <div>
    <ul>
      <li v-for="item in list" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [
        {
          id: 1,
          name: '码云笔记'
        },
        {
          id: '2',
          name: 'mybj'
        },
        {
          id: '3',
          name: 'mybj123.com'
        }
      ]
    }
  }
}
</script>

2.在React中使用v-for的替代语法

react中虽然没有v-for,但是JSX中可以直接使用JS,所以我们可以直接遍历数组

import React from 'react'

export default function() {
  const data = [
    {
      id: 1,
      name: "码云笔记",
    },
    {
      id: "2",
      name: "mybj",
    },
    {
      id: "3",
      name: "mybj123.com",
    },
  ];

  return (
    <div>
      <ul>
        {
        data.map(item => {
          return <li key={item.id}>{item.name}</li>
        })
      }
      </ul>
    </div>
  )
}

v-bind 与 v-on

v-bindVue中是动态绑定属性的,v-on是用于监听事件的,因为React也有属性和事件的概念,所以我们在React也能发现可替代的方式。

1.在Vue中使用v-bindv-on

<template>
  <div>
    <!--:value 是 v-bind:value 的简写, @input 是 v-on:input 的简写-->
    <input :value="value" @input="handleInput" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      value: '码云笔记'
    }
  },
  methods: {
    handleInput(e) {
      this.value = e.target.value
    }
  }
}
</script>

2.在React中寻找替代方案

Vue中,我们将事件和属性进行了分离,但是在React中,其实事件也是属性,所以在本小节我们不仅看一下如何使用属性和事件,再了解一下如何在React中自定义事件

开发一个 CustomInput 组件

import React from 'react'

export interface CustomInputProps {
  value: string;
  //可以看出 onChange 是一个普通的函数,也被定义到了组件的 props 里面了
  onChange: ((value: string,event: React.ChangeEvent<HTMLInputElement>) => void) | undefined;
}

export default function(props: CustomInputProps) {
  
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    // props.onChange 是一个属性,也是自定义的一个事件
    props.onChange && props.onChange(e.target.value, e)
  }

  return (
    <input value={props.value} onChange={handleChange}></input>
  )
}

使用 CustomInput 组件

import React, { useState } from 'react'

import CustomInput from './components/CustomInput'

export default function() {
 const [value, setValue] =  useState('')

 function handleChange(value: string) {
   setValue(value)
 }

  return (
    <div>
      <CustomInput value={value} onChange={handleChange}></CustomInput>
    </div>
  )
}

结语

刚开始从Vue转到React的时候,其实是有点不适应的,但是当慢慢的习惯之后,就会发现VueReact是存在很多共性的,可以参考的去学习。当然无论Vue还是React,上手比较快,但是想深入学习还是需要下功夫的。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 用React.js写法改写Vue写法的教程(二)

发表回复