用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
连插槽都没有, 更别提具名插槽了,但是没有不代表不能模拟出来。对于React
的props
,我们不仅仅可以传入普通的属性,还可以传入一个函数,这时候我们就可以在传入的这个函数里面返回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
。
假设有这样一个场景,系统现在需要提供一个换肤功能,用户可以切换皮肤,现在我们分别使用Vue
和React
来实现这个功能。
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.Provider
将theme
共享出去
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,但也不影响使用
我们知道React
和Vue
都是单向数据流的,即数据的流向都是由外层向内层组件进行传递和更新的,比如下面这段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-model
,v-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
中我们一般绘制页面都会使用到template
,template
里面提供了大量的指令帮助我们完成业务开发,但是在React
中使用的是JSX
,并没有指令,那么我们应该怎么做呢?下面我们就将Vue
中最常用的一些指令转换为JSX
里面的语法(注意: 在 Vue 中也可以使用JSX
)
v-show 与 v-if
在Vue
中我们隐藏显示元素可以使用v-show
或者v-if
,当然这两者的使用场景是有所不同的,v-show
是通过设置元素的display
样式来显示隐藏元素的,而v-if
隐藏元素是直接将元素从dom
中移除掉。
1.看一下Vue
中的v-show
与v-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-show
,v-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-for
在Vue
中是用来遍历数据的,同时我们在使用v-for
的时候需要给元素指定key
,key
的值一般是数据的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-bind
在Vue
中是动态绑定属性的,v-on
是用于监听事件的,因为React
也有属性和事件的概念,所以我们在React
也能发现可替代的方式。
1.在Vue
中使用v-bind
与v-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
的时候,其实是有点不适应的,但是当慢慢的习惯之后,就会发现Vue
和React
是存在很多共性的,可以参考的去学习。当然无论Vue
还是React
,上手比较快,但是想深入学习还是需要下功夫的。
码云笔记 » 用React.js写法改写Vue写法的教程(二)