学习本篇文章,你将收获:
- 快速接入 Vue3.0 的学习开发
- 体验书写 React 的快感
秘诀: 换汤不换药
我们知道,一个 Vue 项目想要改造成 React 项目,实属难于登天,不单单仅限于语法上的差异,背后涉及的生态才是痛点,怎么办呢?试想一下,我们可不可以只改变 Vue 的写法,以 React jsx 的形式来写呢?答案是肯定的,我们今天就来体验一下 React 版本的 Vue 实现,莫急,精彩马上开始!
前置条件: 让 Vue 支持 JSX
-
一个令人振奋的好消息就是:@vue/cli 3.x 脚手架创建的 Vue 项目直接是支持 JSX 的:
// xxx.vue <script> export default { name: 'xxx', ..., render() { return <h3>JSX 渲染</h3> } } </script>运行后,直接能够渲染。
-
如果是以前版本构建的项目则需要安装依赖:
npm install babel-helper-vue-jsx-merge-props babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-preset-env --save-dev// .babelrc { "presets": ["env"], "plugins": ["transform-vue-jsx"] }具体可查看:npmjs 。
我们继续
-
我们在项目
pages目录里面新建一个组件Hooks.js(x),既然是 React 一样写代码,自然后缀名肯定要改正过来。 -
我们需要安装一个包:
vue-hooks,不知道有没有小伙伴使用过。 -
我们从
node_modules里面找到这个包:我们可以发现,里面其实就只有一个有效的index.js,从package.json中也可以看到它没有引入其它任何第三方依赖,其代码量也不足 200 行,这是个好消息(伏笔)。 -
我们可以看一下它导出的所有方法:
{ withHooks, // 通过使用 vue-hooks 包装成 Vue 组件的函数 useState, // 类比:React useState useEffect, // 类比:React useEffect useRef, // 类比:React useEffect useData, // 类比: Vue state useMounted, // 类比: Vue useMounted useDestroyed, // 类比: Vue useDestroyed useUpdated, // 类比: Vue useUpdated useWatch, // 类比: Vue useWatch useComputed, // 类比: Vue useComputed hooks // .vue 文件中使用的钩子 } -
如何使用?我们看一下包里面的
README.md。从里面我们可以看到,vue-hooks提供了三种使用方式。我们首先简单看一下第三种使用方式:- 在
main.js中,import { hooks } from 'vue-hooks' Vue.use(hooks) - 在
.vue文件中,<script> export default { name: 'xxx', ..., hooks() { const data = useData({ count: 0 }) const double = useComputed(() => data.count * 2) return { data, double } } } </script>
return 出来的变量是可以直接作用在
<template>上面的,使用起来是不是很简单? 偷偷告诉你hooks(props) {}这里是可以接收传递的属性参数的。这里我不多赘述,有兴趣的同学可以自己去研究一下,已经超出了本文要讨论的范围哦。我们要重点来看第一种和第二种的写法,毕竟要是 React 风格的嘛~ - 在
-
说到这里,有同学就会问了:为什么要学习两种方式,React style 不是只需要第一种就可以了吗?说的没错!如果是从零开发项目,真的只需要第一种方式就行了,但是,用到组件怎么办?自己造轮子? 不打算使用第三方 UI 组件?这不现实吧!首先要特别强调的一点是:使用
vue-hooksReact style 写代码,只是形势上的变化,实质还是在写Vue Components,千万不要混淆了概念,如我开头所说,换汤不换药,切记! -
由于
vue-hooks现在已经没人维护,不再更新了,对于暴露的接口还有一点点欠缺,为了满足我们的开发需求,兼容 Vue 的 api ,我们来稍加改造一下,找到function withHooks(render):export function withHooks(render) { return { data() { return { _state: {} } }, created() { this._effectStore = {} this._refsStore = {} this._computedStore = {} }, render(h) { callIndex = 0 currentInstance = this isMounting = !this._vnode const ret = render( h, Object.assign( {}, this.$attrs, this.$props, { $store: this.$store, $refs: this.$refs, $router: this.$router, $on: this.$on.bind(this), $emit: this.$emit.bind(this), children: this.$slots } ) ) currentInstance = null return ret } } }现在就大功告成了。
-
还有一点值得注意的是,因为我们改的是
node_modules的文件夹里面的文件, 对于协同开发,其他同学拉取代码得到下载的包依然是未更改过的,这就是个问题, 我们可以把这个改过的文件夹拷贝到工程目录src下,或者根目录下就 ok 了。(回答前面留下的伏笔)。 当然,如果有同学能把更改过的包发布到npm仓库就非常 nice 了。
上手体验一下哈~
且看我实现的一个 demo 案例,稍后作解释:
// Hooks.jsx
import {
withHooks,
useState,
useEffect,
useRef,
useData,
useWatch,
useComputed
} from '@/vue-hooks'
import { Tree, Input } from 'element-ui'
const Hooks = withHooks((h, props) => {
// console.log(props)
// state
const [prop, setProp] = useState('')
const tree = useRef('tree')
const data = useData({
value: ''
})
// effect
useEffect(() => {
props.$store.dispatch('getUser', {
name: '小明',
age: 30
})
}, [])
useWatch(() => data.value, (val, oldVal) => {
console.log(val)
})
useComputed(() => {
setProp(props.$store.getters.getUser.name)
props.$refs.tree && props.$refs.tree.$on('node-click', (value, node) => {
console.log(node)
})
})
const clickHandle = (e) => {
console.log('点我干什么', e.target)
}
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
const watchValue = (val) => {
data.value = val
props.$refs.tree.filter(val)
}
return (
<div>
<h3 onClick={clickHandle}>{ props.aa } This is Hooks Page! {prop}</h3>
<div>
<Input
placeholder="输入关键字进行过滤"
value={data.value}
onInput={watchValue}
style="width: 250px"
/>
<Tree
data={props.dataTree}
show-checkbox
node-key="id"
filter-node-method={filterNode}
indent={35}
check-strictly
ref={tree.current}
/>
</div>
</div>
)
})
Hooks.name = 'Hooks' // 给组件命名,避免出现 <Anonymous Component />
export default Hooks
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {}
},
getters: {
getUser(state) {
return state.user
}
},
mutations: {
getUser(state, payload) {
state.user = payload
}
},
actions: {
getUser({ commit }, payload) {
setTimeout(() => {
commit('getUser', payload)
}, 1000)
}
}
})
// vue.config.js
module.exports = {
publicPath: '/',
outputDir: 'dist',
configureWebpack (config) {},
css: {
requireModuleExtension: true // 开启css modules => xxx.modules.css
},
devServer: {
// proxy: {},
before: app => {}
}
}
解释:
-
store.js 就是存了一个非常简单的 Vue store 数据
-
Hooks.jsx 从第三方 UI 库引入了
element-ui这么个包,引入了{ Tree, Input }两个组件 -
使用方式与 React 一模一样,放在末尾用 return 返回
-
withHooks((h, props) => {})可以看作是 React 函数组件,props作为第二个参数 -
useState、useEffect使用与 React Hooks 的useState、useEffect完全一致 -
useRef使用上有些许区别,const treeRef = useRef('tree') ref={treeRef.current} 或者:ref="tree" ^_^React 直接是
ref={treeRef}, Vue 版的useRef要给它一个名字,之后才能通过props.$refs.tree使用 -
useData类似于 Vue data 提供的值 -
useWatch类似于 Vue watch 接收2个回调函数,第一个为监听的属性,第二个为提供前后变化值的回调函数 -
useComputed类似于 Vue computed,接收一个回调函数 -
注意:
useData和useState的值都可以直接作用于 DOM 节点,触发变更会自动更新,但是useState的值不能够被useWatch、useComputed监听到 -
属性的变化可以被
useWatch、useComputed监听到 -
组件指令属性改造:
v-model ==> value={} onInput={() => {}} v-* ==> { props.xx ? a : b } !!!【class => className; for => htmlFor】✘✘✘ // 不需要改造 v-bind ==> {} @ ==> 1. 原生事件: `onClick={}` 2. `$emit` 事件:`{props.$refs.xxx.$on}` -
Vue 常用的 api 都可以通过
props、props.$store、props.$refs、props.$router、props.$on、props.$emit来完成业务逻辑 -
props.children用来表示vue $slots,匿名插槽通过props.children.default访问, 命名插槽通过props.children.xxx访问 -
有兴趣的同学可以研究一下
vue-hooks各个 api 的源码,代码量不多,总共不到 200 行 -
还有疑问的同学可以留言一起探讨!么么哒~
补充
前文提到修改 node_mudules 代码需要拷贝一份到 src 目录,这里需要纠正一下,原因有:
① 包有其他依赖项,拷贝不现实
② 拷贝的代码无法迭代更新 。
经查阅,解决方案如下:
// vue.config.js
chainWebpack: config => {
config.module
.rule('custom-loader')
.test(/需要改动的文件名.js$/)
.use('custom-loader')
.loader(resolve('loaders/custom-loader'))
.end()
}
// loaders/custom-loader.js
module.exports = function (source) {
const source = '修改的内容';
return source
}
写到最后
怎么样? React 版 Vue 写法是否 get 到了呢?是不是跃跃欲动蠢蠢欲试了呢?赶紧行动起来吧!我爱 React !
本文纯手敲,觉得写得还不错点个赞关注一波呗,如有错别字,或者知识存疑,还请各位大佬指正!