前言:文章不介绍任务背景知识,没有原理说明,偏向于实践的总结和经验分享。
文章所有的代码是基于Vue CLI 3.x版本,不会涉及到一步步通过Webpack来配置JSX所需要的知识点。
在使用Vue开发项目时绝大多数情况下都是使用模板来写HTML,但是有些时候页面复杂又存在各种条件判断来显示/隐藏和拼凑页面内容,或者页面中很多部分存在部分DOM结构一样的时候就略显捉襟见肘,会写大量重复的代码,会出现单个.vue
文件过长的情况,这个时候我们就需要更多的代码控制,这时候可以使用渲染函数。
渲染函数想必平时几乎没有人去写,因为写起来很痛苦(本人也没有写过)。更多的是在Vue中使用JSX语法。写法上和在React中差不多,但是功能上还是没有React中那么完善。
在写JSX的过程中不得考虑一个样式的问题,虽然可以直接在.vue
文件中不写
部分,只写和
部分,而不用担心样式作用域问题。但是更多的时候还是推荐直接使用
.js
的方式来写组件,这个时候就涉及到样式作用域的问题了。
在React的生态中,有很多CSS-IN-JS的解决方案,比如styled-jsx、emotion、styled-components等,目前最活跃和用户量最多的是styled-components,目前已经拥有良好的生态圈子。如果需要在样式中作一些像Sass/Less中的颜色计算,可以使用polished来实现,当然不止这么简单的功能。但是在Vue中可使用的方案就太少了,因为Vue使用模板来写HTML本身是开箱即用的样式 接下来进入正题,从简单语法到经验分享(大牛请绕行) 首先需要约定一下,使用JSX组件命名采用首字母大写的驼峰命名方式,样式可以少的可以直接基于vue-styled-components写在同一个文件中,复杂的建议放在单独的_Styles.js_文件中,当然也可以不采用CSS-IN-JS的方式,使用Less/Sass来写,然后在文件中import进来。 下面是一个通用的骨架: 在JSX中使用单个括号来绑定文本插值 在jsx中不需要把 在JSX中可以直接使用 在JSX中没有 事件绑定需要在事件名称前端加上 注意:如果需要给事件处理函数传参数,需要使用箭头函数来实现。如果不使用箭头函数那么接收的将会是事件的对象 在Vue中基于jsx也可以把组件拆分成一个个小的函数式组件,但是有一个限制是必需有一个外层的包裹元素,不能直接写类似: 必需写成: 而在React中可以使用空标签 那么在Vue中就只能通过遍历来实现类似的功能,大体思路就是把数据先定义好数据然后直接一个 在基础部分简单介绍了事件的绑定用法,这里主要是补充一下事件修饰符的写法。 在模板语法中Vue提供了很多事件修饰符来快速处理事件的冒泡、捕获、事件触发频率、按键识别等。可以直接查看官方文档的事件&按键修饰符部分,这里把相关内容原样搬运过来: 使用方式如下: 下面给出的事件修饰符是需要在事件处理函数中写出对应的等价操作 下面是在事件处理函数中使用修饰符的例子: 在Vue中 注意: 当 假如在jsx中想要引用遍历元素或组件的时候,例如: 会发现从 在jsx中可以使用 等价于模板的 在Vue官方文档中提到:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。因此像下面的示例是无法正常工作的 在 上面的示例其实就是官方的示例,这里需要说明的是,其实在Vue中所谓的作用域插槽功能类似于React中的Render Props的概念,只不过在React中我们更多时候不仅提供了属性,还提供了操作方法。但是在Vue中更多的是提供数据供父作用域渲染展示,当然我们也可以把方法提供出去,例如: 在父组件中使用: 在上面的代码中我们实际上使用解构的方式来取得 如果组件只有一个默认的插槽还可以使用缩写语法,将 上面介绍了很多插槽相关的知识点足已说明其在开发过程中的重要性。说了很多在模板中如何定义和使用作用域插槽,现在进入正题如何在jsx中同样使用呢? 然后在父组件中以jsx使用: 这里需要注意的是在jsx中所有Vue内置的指令除了 对于自定义的指令可以使用 1 直接使用对象传递所有指令属性 2 使用原始的vnode指令数据格式 过滤器其实在开发过程中用得倒是不多,因为更多时候可以通过计算属性来对数据做一些转换和筛选。这里只是简单提及一下并没有什么可以深究的知识点。 在模板中的用法如下: 在jsx中使用方法为: 并不是说我们在开发Vue项目的时候一定要使用jsx的方式来写,但是多掌握一种方式来灵活变通,提高工作效率,扩展思路何尝不值得一试。而且,在有些场景下释放js的完全编程能力会让你更加能够得心应手。其实在使用模板方式的时候我们并没有完全采用组件的思维方式来做,或者说是做得不彻底,不纯粹,拆分的粒度不够。更多 的时候并没有考虑到组件怎么切分和抽象,多人协作的时候如何处理依赖并明确自己的功能点。 在React中所有数据均挂载在 然后在Vue的模板语法中是不区分DOM属性和HTML属性的,例如: 运行示例可以看到 同样运行后会发现在jsx写法中并没有直接将HTML属性初始化为DOM属性值,即输入框中当前值为空字符串,这符合预期的行为。 此外在模板语法中是无法区分HTML属性和DOM属性命名一样的场景,但是在jsx中可以很好的区分: 结果会就是在HMTL中显示 在React中CSS的样式写义在jsx中的语法是以 有使用过Bootstrap经验的可能会注意到它里面包含了很多ARIA属性,这些属性并不属于DOM,在jsx中可以通过 但是上面的换成 在jsx中还可以使用混用的写法,例如在组件中写了 最后需要提及一点的是,在Vue中当给一个组件传了很多props,但是有的并不是组件声明的,也有可能是一些通用的HTML或者DOM属性,但是在最终编译后的HTML中会直接显示这些props,如果不希望这些属性显示在最终的HTML中,可以在组件中设 前面已经把事件相关的知识点都介绍了,这里主要是提及一下关于jsx事件绑定语法 虽然在写组件的时候可以避开命名以 1.使用展开 推荐使用第二种方式,写起来要简单些。 在模板语法中可以使用 True! 然后可以利用 True! 对于复杂的条件判断,例如: Blash Meh hErp Derp 可以采用两种方式来降低判断识别的复杂度 下面是使用IIFE通过内部使用 Blah Meh Herp Derp 还可以使用 Blah Meh Herp Derp 再就是一种比较简单的可选办法,如下: Derp Blah Meh Herp 最后一种使用jsx插件的就不详述和举例了,有兴趣的可以直接查看文档。 在单个jsx文件中可以写很多函数式组件来切分更小的粒度,例如之前的文章Vue后台管理系统开发日常总结__组件PageHeader,组件的形态有两种,一种是普通标题,另一种是带有选项卡的标题,那么在写的时候就可以这样写: 注意在拆分的时候,如果不需要做任何判断可以纯粹是HTML片段赋值给变量,如果需要条件判断就使用函数式组件的方式来写。需要注意的是由于 既然使用函数式组件,那么同样可以在函数中传递参数了,参数是一个对象中,包含了以下属性 虽然可以在函数式组件中传参数、事件、slot但是个人觉得不建议这样做,反而搞复杂了。 我是Slot内容 上面的示例最终生成的HTML中会将 接触Vue时间比较早,但是真正的Vue项目开发经验一年不到,平时比较懒,不怎么去深入学习和研究,所以文章在叙述上没有什么条理性,有些知识点可能并没表达清楚,很多东西还是得多实践去检验。如果有问题欢迎留言共同探讨。 其实早一点实践jsx的写法,对于后面的Vue 3.0出现后可以更快的融入其中,就像React对函数式组件中新增了钩子(Hooks)函数,以后Vue也是主推函数式组件,以后模板语法方式的占比会稍有下降。 文章并没有包含所有Vue中jsx写法的全部知识点,→_→所以叫不完全指南^_^" 最后,感谢各位的支持!!!…(⊙_⊙;)… ○圭~○scoped
,在使用JSX写组件的时候就面临着样式问题,一种方案是在组件包裹基本用法
import styled from 'vue-styled-components'
const Container = styled.div`
heigth: 100%;
`
const Dashboard = {
name: 'Dashboard',
render() {
return (
插值
Message: {this.messsage}
v-model
分成事件绑定和赋值二部分分开来写,因为有相应的babel插件来专门处理。样式
class="xx"
来指定样式类,内联样式可以直接写成style="xxx"
遍历
v-for
和v-if
等指令的存在,这些全部需要采用Js的方式来实现{/* 类似于v-if */}
{this.withTitle &&
事件绑定
on
前缀,原生事件添加nativeOn
event
属性。高级部分
const Demo = () => (
const Demo = () => (
<>>
和
来实现包裹元素,这里的空标签其实只是react.Fragment
的一个语法糖。同时在React 16中直接支持返回数组的形式:const Demo = () => [
map
生成,当然如果说元素的标签是不同类型的那就需要额外添加标识来判断了。{
data() {
return {
options: ['one', 'two']
}
},
render() {
const LiItem = () => this.options.map(option =>
事件修饰符
修饰符
前缀
.passive
&
.capture
!
.once
~
.capture.once
或.once.capture
~!
修饰符
处理函数中的等价操作
.stop
event.stopPropagation()
.prevent
event.preventDefault()
.self
if (event.target !== event.currentTarget) return
按键:
.enter
, .13
if (event.keyCode !== 13) return
(对于别的按键修饰符来说,可将 13
改为另一个按键码)
修饰键:
.ctrl
, .alt
, .shift
, .meta
if (!event.ctrlKey) return
(将 ctrlKey
分别修改为 altKey
、shiftKey
或者 metaKey
)methods: {
keyup(e) {
// 对应`.self`
if (e.target !== e.currentTarget) return
// 对应 `.enter`, `.13`
if (!e.shiftKey || e.keyCode !== 13) return
// 对应 `.stop`
e.stopPropagation()
// 对应 `.prevent`
e.preventDefault()
// ...
}
}
ref和refInFor
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。
$refs
不是响应式的,因此你不应该试图用它在模板中做数据绑定。v-for
用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。const LiArray = () => this.options.map(option => (
this.$refs.li
中获取的并不是期望的数组值,这个时候就需要使用refInFor
属性,并置为true
来达到在模板中v-for
中使用ref
的效果:const LiArray = () => this.options.map(option => (
插槽(v-slot)
this.$slots
来访问静态插槽的内容。
注意:在Vue 2.6.x版本后废弃了
slot
和
slot-scope
,在模板中统一使用新的统一语法
v-slot
指令。
v-slot
只能用于Vue组件和
template
标签。
组件中可以访问到user
属性,但是提供的内容却是在父组件渲染的。如果想要达到期望的效果,这个时候就需要使用作用域插槽了。下面是改写后的代码,更多知识点可以直接查看官方文档的作用域插槽。
injectedProps
,基于解构的特性还可以重命名属性名,在prop
为undefined
的时候指定初始值。v-slot:default="slotProps"
写成v-slot="slotProps"
,命名插槽写成v-slot:user="slotProps"
,如果想要动态插槽名还可以写成v-slot:[dynamicSlotName]
,此外具名插槽同样也有缩写语法,例如 v-slot:header
可以被重写为#header
// current-user components
{
data() {
return {
user: {
firstName: 'snow',
lastName: 'wolf'
}
}
},
computed: {
slotProps() {
return {
user: this.user,
logFullName: this.logFullName
}
}
},
methods: {
logFullName() {
console.log(`${this.firstName} ${this.lastName}`)
}
},
render() {
return (
injectedProps.user
指令
v-show
以外都不支持,需要使用一些等价方式来实现,比如v-if
使用三目运算表达式、v-for
使用array.map()
等。v-name={value}
的语法来写,需要注意的是指令的参数、修饰符此种方式并不支持。以官方文档指令部分给出的示例v-focus
使用为例,介绍二种解决办法:{
directives:{
focus: {
inserted: function(el) {
el.focus()
}
}
},
render() {
const directives = [
{ name: 'focus', value: true }
]
return (
过滤器
{{ message | capitalize }}
注意:由于Vue全局的过滤器只用于模板中,如果需要用于组件的方法中,可以把过滤器方法单独抽离出一个公共Js文件,然后引入组件中,然后用于方法中。
一些简单经验分享
关于DOM属性、HTML属性和组件属性
props
下,Vue则不然,仅属性就有三种:组件属性props
,普通html属性attrs
和DOM属性domProps
。在Angular的文档中关于插值绑定部分是重点说明了DOM属性和HTML属性的区别,在大多数情况下两者都有对应的同名属性,也就是1:1映射关系,但是也有例外的情况,比如HTML中colspan
,DOM中的textContent
。HTML属性的值指定了初始值,并且不能改变,而DOM属性的值表示当前值,是可以改变的。
input
的初始值被设置为了“我是DOM属性值",当我们在输入框中添加或者删除文字时,HTML属性始终没有变化,而绑定的DOM值一值在变动。然后再看一下在jsx中的实现:title="我是DOM属性
,而"我是组件属性”传递给了组件。className="xx"
的形式,而在Vue的jsx中可以直接写成class="xx"
。实际上由于class
是Js的保留字,因此在DOM中其属性名为className
而在HTML属性中为class
,我们可以在Vue中这样写,经过Babel转译后得到正确的样式类名:
注意:如果同时写了
class="xx" domPropsClassName="yy"
那么后者的优先级较高,和位置无关。所以尽量还是采用
class
的写法。
attrsXX
或者直接aria-xx
的方式来添加:
domPropsAria-label
就没有任何效果。
注意:在jsx中所有DOM属性(
Property)语法为
domPropsXx
, HTML特性(
Attribute)语法为
attrsXx
。更多的时候建议还是少使用,或者说合理使用。
,还可以定义一个属性对象,然后使用{...props}
的方式写在一起,这个时候就会出现属性合并的问题,同样的事件多个地方声明事件处理函数,都会触发。inheritAttrs: false
。虽然不显示了,但是我们依然可以通过vm.$attrs
获取所有(除class
和style
)绑定的属性,包括不在props中定义的。关于事件
onXx
和组件属性(主要是函数prop)以on
开头的情况如何处理。on
开头,但是在使用第三库的时候,如果遇到了该如何处理呢?比如Element组件Upload很多钩子都是以on
开头。 下面提供两种解决办法:
propsXx
复杂逻辑条件判断
v-if
、v-else-if
和v-else
来做条件判断。在jsx中可以通过?:
三元运算符(Ternary operator)运算符来做if-else
判断:const Demo = () => isTrue ?
&&
运算符的特性简写为:const Demo = () => isTrue &&
const Demo = () => (
if-else
返回值来优化上述问题:const Demo = () => (
do
表达式,但是需要插件@babel/plugin-proposal-do-expressions的转译来支持,const Demo = () => (
const Demo = () => {
const basicCondition = flag && flag1 && !flag3;
if (!basicCondition) return
组件的传值
render() {
// partial html
const TabHeader = (
)
// function partial
const Header = () => (
)
render
函数会多次被调用,写的时候注意一下对性能的影响,目前能力有限这方面就不作展开了。children # VNode数组,类似于React的children
data # 绑定的属性
attrs # Attribute
domProps # DOM property
on # 事件
injections # 注入的对象
listeners: # 绑定的事件类型
click # 点击事件
...
parent # 父组件
props # 属性
scopedSlots # 对象,作用域插槽,使用中发现作用域插槽也挂在这个下面
slots # 函数,插槽
render() {
const Demo = props => {
return (
Jsx中的内部组件 { props.data.title }
{ props.children }
{ props.scopedSlots.bar() }
我是Children
的内容转换为
#document-fragment
。总结