插槽,在 React 中没找到? 在使用 Vue 的时候,插槽是一个特别常用的功能,通过定义插槽,可以在调用组件的时候将外部的内容传入到组件内部,显示到指定的位置。在 Vue 中,插槽分为默认插槽,具名插槽和作用域插槽。其实不仅仅 Vue,在 React 中其实也有类似插槽的功能,只是名字不叫做插槽,下面我将通过举例来说明。
默认插槽 现在项目需要开发一个卡片组件,如下图所示,卡片可以指定标题,然后卡片内容可以用户自定义,这时候对于卡片内容来说,就可以使用插槽来实现,下面我们就分别使用 Vue 和 React 来实现这个功能
Vue 实现 实现一个 card 组件,如下代码所示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <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 >
在外部使用定义的 card 组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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 默认插槽的功能,一起看看代码。
使用 React 定义 Card 组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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} </div> </div> ); }
在外部使用 Card 组件 1 2 3 4 5 6 7 8 9 10 11 12 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 的具名插槽主要解决的是一个组件需要多个插槽的场景,其实现是为添加 name 属性来实现了。
我们就上面的需求对 card 组件进行修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template > <div class ="card" > <div class ="card__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 >
card 组件修改完之后,我们再去调整一下使用 card 组件的地方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > <div > <my-card > <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,从而就实现了具名插槽的功能。
对原有的 Card 组件进行修改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React from "react" ;export interface CardProps { title?: string; renderTitle?: Function ; children: React.ReactNode; } export default function (props: CardProps ) { const { title, renderTitle } = props; 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> ); }
这时候就可以在外部自定义 title 了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template > <custom-card title ="人员信息卡片" > <div class ="content" > <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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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 > ; }
在外部使用人员信息卡片组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 内容为以下内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template > <div id ="app" > <router-view /> </div > </template > <script > export default { data() { return { themeInfo: { theme: "dark" , }, }; }, provide() { return { theme: this .themeInfo, }; }, }; </script >
在任意层级的子组件中像下面这样使用 1 2 3 4 5 6 7 8 <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 文件,然后添加以下内容 1 2 3 4 5 import { createContext } from "react" ;export const MyContext = createContext({ theme: "light" , });
MyContext 提供了一个 Provider,通过 Provider 可以将 theme 共享到所有的子组件。现在我们在所有的组件的共同父组件比如 App.js 上面添加 MyContext.Provider 将 theme 共享出去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { MyContext } from "@/context/MyContext" ;export default function ( ) { const [theme, setTheme] = useState("dark" ); return ( <MyContext.Provider value={{ theme, }} > <Children></Children> </MyContext.Provider> ); }
这时候就可以直接在所有的子组件里面使用定义的主题 theme 了 1 2 3 4 5 6 7 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 代码就是标准的单向数据流.
1 2 3 4 5 6 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 组件拥有双向数据绑定的能力,比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <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 事件来实现的,比如这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <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 语法糖,但是也可以通过传入属性然后监听事件来实现数据的双向绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 事件合并成一个,比如像下面代码这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 中移除掉。
看一下 Vue 中的 v-show 与 v-if 的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <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 >
将 v-show,v-if 转换为 JSX 中的语法 在 Vue 中指令是为了在 template 方便动态操作数据而存在的,但是到了 React 中我们写的是 JSX,可以直接使用 JS,所以指令是不需要存在的,那么上面的 v-show,v-if 如何在 JSX 中替代呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React, { useState } from "react" ;export default function ( ) { const [showName] = useState(false ); const [showDept] = useState(true ); const [userInfo] = useState({ name: "子君" , dept: "银河帝国" , }); return ( <div> {} <span style={{ display : showName ? "block" : "none" }}> {userInfo.name} </span> {} {showDept ? <span > {userInfo.dept}</span > : undefined } </div> ); }
v-for v-for 在 Vue 中是用来遍历数据的,同时我们在使用 v-for 的时候需要给元素指定 key,key 的值一般是数据的 id 或者其他唯一且固定的值。不仅在 Vue 中,在 React 中也是存在 key 的,两者的 key 存在的意义基本一致,都是为了优化虚拟 DOM diff 算法而存在的。
在 Vue 中使用 v-for 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <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 >
在 React 中使用 v-for 的替代语法 在 react 中虽然没有 v-for,但是 JSX 中可以直接使用 JS,所以我们可以直接遍历数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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-bind 与 v-on v-bind 在 Vue 中是动态绑定属性的,v-on 是用于监听事件的,因为 React 也有属性和事件的概念,所以我们在 React 也能发现可替代的方式。
在 Vue 中使用 v-bind 与 v-on 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div > <input :value ="value" @input ="handleInput" /> </div > </template > <script > export default { data() { return { value: "子君" , }; }, methods: { handleInput(e) { this .value = e.target.value; }, }, }; </script >
在 React 中寻找替代方案 在 Vue 中,作者将事件和属性进行了分离,但是在 React 中,其实事件也是属性,所以在本小节我们不仅看一下如何使用属性和事件,再了解一下如何在 React 中自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React from "react" ;export interface CustomInputProps { value: string; onChange: | ((value: string, event: React.ChangeEvent<HTMLInputElement> ) => void ) | undefined ; } export default function (props: CustomInputProps ) { function handleChange (e: React.ChangeEvent<HTMLInputElement> ) { props.onChange && props.onChange(e.target.value, e); } return <input value ={props.value} onChange ={handleChange} > </input > ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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> ); }
数据 data,在 react 中叫 state 熟悉 vue 的小伙伴一定对 Vue 中的 data 不会感到陌生的,反正天天写 Bug 的时候都要用,但是对于 data 来说,在 Vue2.0,Vue3.0,React 中用法是不同的,我们下面依次举例说明
Vue2.0 中的用法 1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div > {{ name }}</div > </template > <script > export default { data() { return { name: "子君" , gzh: "前端有的玩" , }; }, }; </script >
通过上面的代码我们可以看到 data 是一个函数,然后函数中返回了一个对象,那么为什么 data 是一个函数呢?比如我们有时候也会在 App.vue 文件中看到 data 不是函数的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div id ="app" > <router-view /> </div > </template > <script > export default { data: { name: "子君" , sex: "男" , }, }; </script >
那么为什么我们在普通组件里面还要将 data 声明为函数呢?
Vue 官网是这样解释的:当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。 如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用 同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。 而 App.vue 可以将 data 声明为一个普通对象是因为整个系统中 App.vue 只会被使用到一次 ,所以不存在上述的问题。
Vue3 中的用法 在 Vue3 中,我们依然可以像 Vue2 那样去使用 data,当然 Vue3 提供了新的 Composition API,在后续文章中,如果没有特殊说明,我们提到 Vue3 就默认指的是使用 Composition API。 在 Composition API 提供了响应式 API,分别是 ref 和 reactive,通过这两个 API 可以生成响应式的数据
基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template > <div class ="home" > <div > 姓名:{{ state.name }}</div > <div > 公众号:{{ state.gzh }}</div > <div > 统计:{{ count }}</div > </div > </template > <script lang ="ts" > import { defineComponent, reactive, ref } from "vue" ; export default defineComponent({ name: "Home" , setup() { const state = reactive({ name: "子君" , gzh: "前端有的玩" , }); const count = ref(0 ); return { state, count, }; }, }); </script >
响应数据修改 在 Vue2.0 中,我们修改 data 的方式一般会使用 this.name = '张三'这种赋值的方式,但是对于 Composition API 中因为提供了两种 api,所以用法稍有区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template > <div class ="home" @click ="handleClick" > <div > 姓名:{{ state.name }}</div > <div > 公众号:{{ state.gzh }}</div > <div > 统计:{{ count }}</div > </div > </template > <script lang ="ts" > import { defineComponent, reactive, ref } from "vue" ; export default defineComponent({ setup() { const state = reactive({ name: "子君" , gzh: "前端有的玩" , }); const count = ref(0 ); function handleClick ( ) { state.name = "张三" ; count.value++; } return { state, count, handleClick, }; }, }); </script >
如上代码所示 :
对于 reactive 声明的数据 对于 reactive,我们可以通过 state.name 来获取数据,然后通过 state.name='张三'来修改数据
对于 ref 声明的数据 对于 ref 声明的数据,ref 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value。所以我们在代码中获取 ref 对象的数据需要使用 count.value 的方式,修改值的方式也需要通过 count.value++的方式。 但是这里有一个特殊的点就是在 template,ref 对象会自动解套,意思就是对于<div>统计:{{ count }}</div>,代码里面可以直接使用 count,而不需要写成 count.value,Vue 自己就会将其解套为 count.value。
React 中的用法 React16.8 新增了 Hook 特性,现在许多团队已经大规模使用了,所以本文的内容更多的是以 Hook 为主。 在 Vue3.0 中提供了 Composition API,其实这个和 React 的 hook 用法是很相似的,接下来我们将上文中我们写的 Vue3.0 代码修改为 React 版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import React, { useState } from "react" ;export default function ( ) { const [name, setName] = useState("子君" ); const [gzh] = useState("前端有的玩" ); function handleClick ( ) { setName("张三" ); } return ( <div onClick={handleClick}> <div>姓名:{name}</div> <div>公众号: {gzh}</div> </div> ); }
在这段代码中我们使用到了 useState 声明了一个 state 变量,useState 返回的值是一个数组,然后我们通过数组解构获取到了两个变量, const [name, setName] = useState('子君'), 其中 name 对应声明的 state 变量,而 setName 是一个函数,调用 setName 可以修改变量的值,比如 setName('张三'),这时候 name 的值就会变成了张三
侦听器 watch,监督你没毛病 在平常开发中是比较常用 watch 的,使用 watch 可以去监听数据的变化,然后在变化之后做一系列的操作。比如有一个列表页,我们希望用户在输入搜索关键字的时候,可以自动触发搜索。此时除了监听输入框的 input 事件之外,还可以通过 vue 的 watch 来监听关键字的变化
Vue2.0 中的写法 在 vue2.0 中,watch 常用的写法包含了两种,下面我们分别使用不同的写法来进行上述功能的实现
常规实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template > <div > <div > <span > 搜索</span > <input v-model ="searchValue" /> </div > </div > </template > <script > export default { data() { return { searchValue: "" , }; }, watch: { searchValue(newValue, oldValue) { if (newValue !== oldValue) { } }, }, }; </script >
使用 $watch 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template > <div > <div > <span > 搜索</span > <input v-model ="searchValue" /> </div > </div > </template > <script > export default { data() { return { searchValue: "" , }; }, created() { const unwatch = this .$watch("searchValue" , (newValue, oldValue ) => { if (newValue !== oldValue) { } }); }, }; </script >
在调用$watch 的时候,会有一个返回值 unwatch,然后如果需要取消 watch 监听,我们可以通过调用 unwatch 来进行,比如有一个表单,表单上面的保存按钮平常是置灰的,但是假如用户对表单进行了修改,就需要将表单的置灰状态修改为启用状态。但是如果表单已经启用了,就没必要继续 watch 了,这时候就需要使用 unwatch
Vue3.0 中的写法 在 Vue3.0 中除了 Vue2.0 中的写法外,还在 Composition API 提供了 watch 与 watchEffect 两个 API,用于监听数据的变化,下面我们将上面搜索分别使用 watch 与 watchEffect 来实现
watch 实现方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template > <div > <span > 搜索</span > <input v-model ="state.searchValue" /> </div > </template > <script lang ="ts" > import { defineComponent, reactive, watch } from "vue" ; export default defineComponent({ setup() { const state = reactive({ searchValue: "" , }); const unwatch = watch( () => state.searchValue, (value, oldValue) => { if (value !== oldValue) { } } ); return { state, }; }, }); </script >
watch API 与 Vue2.0 中的 this.$watch 用法基本是一致的,包括使用的参数等,同时 watch API 返回了 unwatch 函数用于取消 watch 同时 watch 还可以侦听多个属性的变化,就像下面这样
1 watch([a, b, c], ([a, b, c], [oldA, oldB, oldC] ) => {});
watchEffect 实现watchEffect 参数是一个函数,在代码执行时,会立即执行 watchEffect 传入的函数,然后响应式追踪其依赖,并在其中某些依赖发生变化时重新运行该函数。我们将上述搜索代码使用 watchEffect 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default defineComponent({ setup() { const state = reactive({ searchValue: "" , }); function loadData (searchValue ) {} const unwatch = watchEffect(() => { loadData(state.searchValue); }); return { state, }; }, });
React 中的用法 在 React 中与 watch 比较相似的功能是 Effect Hook,使用它可以让你在函数组件中执行副作用操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import React, { useEffect, useState } from "react" ;export default function ( ) { const [searchValue, setSearchValue] = useState("" ); function handleChange (e: React.ChangeEvent<HTMLInputElement> ) { setSearchValue(e.target.value); } useEffect(() => { console .log(111 ); }, [searchValue]); return ( <div> <input value={searchValue} onChange={handleChange}></input> </div> ); }
如上代码我们使用 useEffect 来监听 searchValue 的变化,然后触发新的逻辑,但是看到上面代码,我们并没有发现取消 effect 的方法,那么如何取消呢?useEffect 第二个参数是一个数组,通过给数组传入要监听的变量来实现数据监听,但是却没有办法去取消这个监听,所以我们需要曲线救国,就像下面代码这样
1 2 3 4 5 6 7 8 9 const [isWatch] = useState(true );useEffect(() => { if (isWatch) { console .log(searchValue); } }, [isWatch, searchValue]);
计算属性,在 React 中我也找到的踪迹 Vue 中的计算属性,相信大家都很熟悉,通常我们会使用计算属性来对 template 中的复杂逻辑计算进行简化,比如许多英文网站输入用户名的时候会输入 firstName 和 lastName,然后在界面上面又会将 firstName 和 lastName 连在一起显示,这时候就可以使用到了计算属性对显示进行处理
Vue2.0 中的写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template > <div > <div > <label > firstName</label > <input v-model ="firstName" /> <label > lastName</label > <input v-model ="lastName" /> </div > <div > 用户名:{{ name }}</div > </div > </template > <script > export default { data() { return { firstName: "" , lastName: "" , }; }, computed: { name() { return this .firstName + "·" + this .lastName; }, }, }; </script >
Vue3.0 中的写法 在 Vue3.0 的 Composition API 也提供了 computed API,用于生成计算属性,用法与 Vue2.0 用法基本是一致的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { computed, defineComponent, reactive } from "vue" ;export default defineComponent({ setup() { const state = reactive({ firstName: "" , lastName: "" , }); const name = computed(() => state.firstName + "·" + state.lastName); return { state, name, }; }, });
React 中的写法 在说到在 React 中模拟计算属性之前,我们先要了解一些 React Hook 的规则。
只能在最顶层使用 Hook 只能在 React 函数中调用 Hook
当我们在 React 函数中使用 useState 之后,如果我们通过 setState 修改了 state,那么这时候 react 会做什么呢?React 会将这个函数式组件重新执行一遍,但是对于里面的 useState,useEffect 等等不会重新初始化,而是使用已经记录的状态进行处理。那么 React 是怎么知道哪个 state 对应哪个 useState 呢?答案是 React 靠的是 Hook 调用的顺序。所以我们不能在非顶层比如 if 里面使用 Hook。 同时呢?因为 state 的变化会引起整个函数重新执行,那么假如我们在代码里面写了这样一段逻辑
1 2 3 4 5 6 const [firstName, setFirstName] = useState("" );const [lastName, setLastName] = useState("" );const [other, setOther] = useState("" );const name = firstName + "·" + lastName;
上面代码里面我们的 name 是通过 firstName 与 lastName 计算而来的,那么当 firstName 或者 lastName 发生变化时,都会重新计算 name,这个逻辑是正确的。但是实际上 other 如果发生了变化,也会导致 name 重新计算,这是我们不愿意看到的。假如 name 的计算逻辑很复杂,那么就会引起不必要的性能开支。所以 React 提供了 useMemo,用于避免非相关属性变化引起计算逻辑发生变化,而我们正好可以利用 useMemo 来模拟计算属性,如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React, { useMemo, useState } from "react" ;export default function ( ) { const [firstName, setFirstName] = useState("" ); const [lastName, setLastName] = useState("" ); const name = useMemo(() => firstName + "·" + lastName, [firstName, lastName]); const handleChange = ( method: Function , e: React.ChangeEvent<HTMLInputElement> ) => { method(e.target.value); }; return ( <div> <div> <label>firstName</label> <input value={firstName} onChange={(e ) => handleChange(setFirstName, e)} /> <label>lastName</label> <input value={lastName} onChange={(e ) => handleChange(setLastName, e)} /> </div> <div>用户名:{name}</div> </div> ); }
但是呢,在 Vue 中计算属性既可以 get,也可以 set,这一点我们是无法使用 useMemo 来模拟的