适合Vue用户的React教程,你值得拥有

双节旅游人如山,不如家中代码闲。 学以至用加班少,王者荣耀家中玩。javascript

小编平常工做中使用的是Vue,对于React只是作过简单的了解,并无作过深刻学习。趁着这个双节假期,小编决定好好学一学React,今天这篇文章就是小编在学习React以后,将ReactVue的用法作的一个对比,经过这个对比,方便使用Vue的小伙伴能够快速将Vue中的写法转换为React的写法。前端

本文首发于公众号【前端有的玩】,玩前端,面试找工做,就在【前端有的玩】vue

插槽,在React中没找到??

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

默认插槽

如今项目须要开发一个卡片组件,以下图所示,卡片能够指定标题,而后卡片内容能够用户自定义,这时候对于卡片内容来讲,就可使用插槽来实现,下面咱们就分别使用VueReact来实现这个功能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组件redux

    <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默认插槽的功能,一块儿看看代码。markdown

  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>
      );
    }
    复制代码
    1. 在外部使用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>

复制代码
  1. 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>
  );
}
复制代码
  1. 这时候就能够在外部自定义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>

复制代码
  1. 在外部使用人员信息组件
<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('子君')
  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('子君')

  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-showv-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: '银河帝国',
          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: '张三'
            },
            {
              id: '3',
              name: '李四'
            }
          ]
        }
      }
    }
    </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: "张三",
        },
        {
          id: "3",
          name: "李四",
        },
      ];
    
      return (
        <div>
          <ul>
            {
            data.map(item => {
              return <li key={item.id}>{item.name}</li>
            })
          }
          </ul>
        </div>
      )
    }
    复制代码

v-bindv-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,上手比较快,可是想深刻学习仍是须要下功夫的,后续小编将会对VueReact的用法在作更深刻的介绍,敬请期待。

相关文章
相关标签/搜索