回想起自己刚开始同时维护小程序项目、vue项目时,各种语法乱入,伴随着各种错误自己整理了一篇笔记,一直想写篇学习小结,拖到后来学习了react,最近又学习了vue3,笔记越来越长,终于动工了,下面是我的学习小结。
一、模板语法
1、小程序
文本插值语法:{{data}}
属性绑定语法:prop="{{data}}"
prop="xx{{data}}xx"
使用双引号,undefined
值不会被输出到wxml
中。=后""内的语法类似模板字符串 `xx${data}xx`
数据来源:data
,properties
中声明
运算支持:支持在{{}}
内进行三元运算、算术运算、逻辑判断、字符串运算,但不支持调用 Math.random()
,Number()
,String.prototype.slice()
,new Date()
等方法。
注:变量一定出现在双大括号内。
<view> {{ key }} </view>
<view id="{{data}}"> </view>
<view id="item-{{data}}"> </view>
<checkbox checked="{{false}}"> </checkbox>
<input model:value="{{value}}" /> //简易双向绑定语法
<input model:value="{{ a.b }}" /> //错误:简易双向绑定,不支持属性路径
2、Vue2、Vue3
文本插值语法:{{data}}
属性绑定语法:v-bind:prop="data"
:prop="data"
:prop="foo(data)"
=后""内的语法是JavaScript表达式。
数据来源:
- Vue2:
data
,computed
,props
中声明 - Vue3:
setup
中ref
,reactive
,computed
,defineProps
等声明的变量
运算支持:{{}}
和 v-bind
指令支持完整的JavaScript表达式,支持调用有限的全局对象,如Math和Date。
<p> {{ key}} </p>
<div :id="data"> </div>
<div :id="'item_' + data"> </div>
<button :disabled="false"></button>
<a :[attributeName]="url"> ... </a> //动态参数
<UserName v-model:first-name="first" v-model:last-name="last" /> vue3多个v-model绑定
3、React
采用JSX
语法,all in js: {this.state.key}
{key}
prop={data}
{}
内的是JavaScript表达式或React元素。
数据来源:
- Class组件:
state
中声明 - 函数式组件Hooks:return 之前声明的常量、使用
useState
声明的变量
运算支持:{}
可放置任何有效的 JavaScript 表达式、React元素
<p> {{ this.state.key }} </p> //class组件state中声明的变量
<p> {{ key }} </p> //函数式组件useState声明的变量
<div id={data}> </div>
<div id={'item_' + data}> </div>
{<h1>hello</h1>}
二、列表渲染
1、小程序
语法:wx:for="{{}}"
wx:for-item='itemName'
指定当前项变量名,默认 item; wx:for-index=''
指定当前项下标变量名,默认 index;wx:key
项的唯一标识。
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key='{{itemName.id}}'> //遍历数组
<view wx:for="abc"> //遍历字符串,字符串解析成字符串数组
2、Vue2、Vue3
语法:v-for="item in items"
<li v-for="(item, index) in items" :key="">{{ item.message }}</li> //遍历数组
<div v-for="(value, key, index) in object" :key=""> {{ key }}: {{ value }}</div> //遍历对象属性值、属性名、位置索引
<span v-for="n in 10" :key="">{{ n }} </span> //遍历数值范围,n从1开始
3、React
语法:JS数组遍历方法
{
this.state.list.map(todo => {
return (
<Todo key={todo.id} {...todo} />
)
})
}
三、条件渲染
1、小程序
语法:wx:if
wx:elif
wx:else
<view wx:if="{{length > 5}}"></view>
<view wx:elif="{{length > 2}}"></view>
<view wx:else></view>
<block wx:if="{{condition}}"> </block> //<block/>包装元素,不会在页面中做任何渲染,只接受控制属性。
2、Vue2、Vue3
语法:v-if
v-else-if
v-else
<div v-if="Math.random() > 0.5"></div>
<h1 v-show="condition">Hello!</h1>
3、React
语法:JS三元运算符、逻辑运算符
{this.state.isLogin ? (<div>欢迎xxx</div>) : (<div>请登录</div>) }
{this.state.isLogin && <CustomComponent /> }
四、样式绑定
1、小程序
样式属性名使用短横线分隔(kebab-case
) ,不能使用驼峰式 (camelCase
) ,否则不识别。
类似模板字符串语法:样式属性名不需要添加引号,属性值若是变量,放在{{}}
内
注:相关文档 推荐使用rpx
自适应单位,UI 使用750px
的设计图,字号推荐使用(设计图尺寸/2)px
(不使用rpx
单位因为会跟随屏幕宽度而缩放),其他尺寸全部使用rpx
(与设计图1:1
写css
数值), 比如元素宽高、元素边距等。
class="className1 {{ condition ? 'className2' : ''}}"
style="padding-top:{{data}}px; background-color:{{data}}"
2、Vue2、Vue3
:class="{className1: condition, className2: condition}" //绑定class名
:class="classObject" //绑定对象 classObject可以是data或计算属性中的变量
:class="[className1, className2, condition?className3:'']" //绑定class名组成的数组
:style="{paddingLeft: '10px', 'margin-left': value }" //css样式对象,属性名可用驼峰式 (camelCase) 或短横线分隔(kebab-case)
:style='styleObject' //styleObject可以是data或计算属性中的变量
:style='[styleObject1, styleObject2]' //css样式对象组成的数组
3、React
className={"foo bar baz"}
className={'foo ' + isActive?'active':'' }
className={['foo', isActive?'active':''].join(' ')}
//使用classnames库
className={classNames('foo','bar','baz')}
className={classNames('foo',{'active':isActive})}
//行内样式,传入对象而不是css字符串,css属性采用小驼峰写法
style={{fontSize:'14px',margin:'20px 0'}}
css in js:styled-components
import styled from 'styled-components';
export const Wrapper = styled.div`
font-size: 12px;
p{
font-size:16px;
}
}
`
<Wrapper></Wrapper> //引用定义的样式组件
五、取值、更新值
1、小程序
this.data.key this.setData({key: value})
this.data.obj.key this.setData({'obj.key': value})
2、Vue2、Vue3
Vue2:相关文档
- 创建 vue 实例时 data 中存在的属性是响应式的,为 data 中对象新增属性需要通过
Vue.set()
或者this.$set()
- 数组通过
push
pop
shift
unshift
splice
sort
reverse
这几个方法修改是响应式的 - 使用新的对象赋值是响应式的
data(){
return {
key:'',
obj:{key:''}
}
}
this.key this.key=value
this.obj.key this.obj.key = value
Vue.set(this.obj, newKey, value) this.$set(this.obj,newKey,value)
this.obj = Object.assign({}, this.obj, { newKey: value })
Vue3:相关文档
- ref 通过
.value
赋值。使用ref
声明数组可传入泛型,避免类型被推断为Ref<never[]>
。 - 不能替换一个响应式对象进行赋值,只能给响应式对象的属性赋值,否则丢失响应式代理。
- 依靠深层响应性,响应式对象内的嵌套对象依然是代理:嵌套的响应式对象属性,可整体修改为新的普通对象或新的响应式对象,后续更新仍然是响应式的。
// 1. ref
let flag = ref(true)
flag.value = false
const multipleSelection = ref<any[]>([])
// 2. reactive
const obj = reactive({ aa: { bb: 1, } })
obj = {} //丢失代理
obj.aa.bb = 2 //响应式更新
// 3. reactive
const obj2 = { bb: 11 }
obj.aa = obj2 //响应式更新
const obj2 = reactive({ bb: 2, })
obj.aa = obj2 //响应式更新
3、React
setState
Class组件,setState
被设计成异步的,不会立即更新组件,使用回调函数或componentDidUpdate
获取更新后的数据
this.state.key
this.setState({key: value})
useState 函数式组件useState
,异步更新,无this
const [count, setCount] = useState(0);
setCount(count+1)
useEffect(() => {
//...
}, [count])
六、事件绑定
1、小程序
bindtap
bind:tap
两种写法均可,普通事件绑定 bindtap="handlerTap"
, 数据绑定 bindtap="{{ condition ? 'handlerTap':'' }}"
结果必须是字符串,当变量值为假值时解除绑定。
catchtap="handleTap"
阻止事件冒泡
data-key
绑定属性值,会被传入事件对象的dataset
。多个单词由连字符 - 连接会转换成小驼峰写法,如果使用大写字符会转成全小写字符。
<view id="tapTest" data-hi="Weixin" data-current-index='value' data-currentKey='value' bindtap="foo"> Click me! </view>
Page({
methods:{
foo: function(e) {
console.log(e.target.dataset) //{currentIndex: "value", currentkey: "value", hi: "Weixin"}
}
}
})
2、Vue2、Vue3
v-on:EventType='methodName(item, $event)'
表达式/函数名/传参、不传参函数调用语句。$event
合成对象,原始的 DOM事件对象 + vue封装的属性(key按键修饰符、target可以是子组件等)。
监听原生DOM事件时,如果该方法不需要额外参数,那么方法后的()可以不添加,$event
作为唯一的参数被默认传递。
如果需要同时传入多个参数,且需要使用事件参数event
时,可以通过$event
显示传入。
- 事件修饰符:
.stop
stopPropagation()
,.prevent
preventDefault()
,.capture
监听事件捕获,.self
子元素不触发,.once
事件触发一次 - 按键修饰符别名:
@keyup.alt.67=''
.enter
.tab
.delete
.esc
.space
.up
.down
.left
.right
.ctrl
.shift
.alt
.meta
ViewModel 被销毁时,所有的事件处理器都会自动被删除。
3、React
Class组件:传入箭头函数,在箭头函数内调用回调函数。箭头函数继承(词法作用域)作用域链上一层的this
,起到绑定this
的效果。回调函数被调用的形式,而不是被当做参数传入子组件,方便传递额外的参数。
onClick={(e)=>{this.handleName(param,e)}}
函数式组件,无this
。
onClick={(e)=>{handleName(param,e)}}
七、标签
1、小程序
不支持常用的HTML
标签div
等,<view></view>
小程序提供的容器标签
图片标签 <image />
<van-image></van-image>
<!-- 注释 -->
2、Vue2、Vue3
支持HTML
标签,ElementUI
组件标签,Vant
组件标签
图片标签 <img />
<el-image></el-image>
<van-image></van-image>
<!-- 注释 -->
3、React
支持HTML
标签,styled-component
样式标签
图片标签 <img />
<Image />
{/* 注释 */}
八、生命周期函数
1、小程序
页面生命周期:onLoad
,onShow
,onPullDownRefresh
,onUnload
,onShareAppMessage
,onPageScroll
,onResize
,onReachBottom
,onReady
,onHide
组件生命周期:lifetimes:{attached, moved, detached}
,pageLifetimes:{show, hide, resize}
2、Vue2、Vue3
Vue2:
- beforeCreate, created。
created
函数:可以进行ajax
请求,可使用this
为data
赋值,不能操作 DOM。 - beforeMount, mounted。
mounted
阶段:vue
实例挂载完成,DOM 已渲染,可通过this.$refs
操作 DOM。 - activated, deactivated
- beforeUpdate, updated。
beforeUpdate
:修改数据不会造成重新渲染,updated
:不可更新数据,可能造成死循环。 - beforeDestory, destoryed。
beforeDestory
:可清除计时器等,destroyed
:实例销毁,数据绑定卸除,子实例销毁。
Vue3:
onMounted
,onUpdated
,onUnmounted
,onBeforeMount
,onBeforeUpdate
,onBeforeUnmount
,onActivated
,onDeactivated
3、React
Class组件:componentDidMount
,componentDidUpdate
,componentWillUnmount
函数式组件Hooks: useEffect
模仿生命周期 componentDidMount
、componentDidUpdate
九、提示消息
1、小程序
wx.showLoading({title: '',})
wx.hideLoading();
wx.showToast({
title: '成功',
icon: 'success',
duration: 2000
})
wx.showModal({
title: '提示',
content: '这是一个模态弹窗',
success (res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
2、Vue2、Vue3
3、React
十、获取路由参数、路由跳转
1、小程序
let routes = getCurrentPages()
let currentRoute = routes[routes.length-1].route
路由跳转:
wx.navigateTo({url:''})
wx.switchTab({url:''}) //路径后不能带参数
wx.redirectTo()
wx.relaunch()
wx.navigateBack()
2、Vue2、Vue3
// Vue2
let currentRoute = this.$route
// Vue3
import { useRoute } from 'vue-router'
const currentRoute = useRoute()
路由跳转:
// Vue2
this.$router.push({path:''})
this.$router.replace()
this.$router.go()
this.$router.back()
// Vue3
import { useRouter } from 'vue-router'
const router = useRouter()
router.push()
3、React
import { useLocation } from 'react-router'
const location = useLocation()
路由跳转:
import { useHistory } from 'react-router-dom'
const history = useHistory()
history.push()
十一、组件通信
1、小程序
- 通过拼接
URL
参数传值,在被打开的页面通过onLoad(query)
参数获取 - 通过设置全局
app.js
中globalData
,在其他页面通过const app = getApp(); app.globalData.key
获取,随着小程序卸载而重置(杀掉小程序进程,小程序冷启动) - 本地缓存,持久化存储
wx.setStorage({})
wx.getStroage({key:'', success:function(res{res.data}})
,存储登录信息等,杀掉小程序进程再次打开时,仍能保留登录数据。 - 通过
wx.navigateTo()
返回的EventChannel
。web-view
载入的H5
,navigateTo
跳转小程序页面,不支持EventChannel
。
wx.navigateTo({
url: 'test?id=1',
events: {
someEvent: function(data) { // 监听 被打开页面传送到当前页面的数据
console.log(data)
}
},
success: function(res) {
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' }) // 通过eventChannel向 被打开页面传送数据
}
})
//test.js
Page({
onLoad: function(option){
const eventChannel = this.getOpenerEventChannel()
eventChannel.emit('someEvent', {data: 'test'});
eventChannel.on('acceptDataFromOpenerPage', function(data) {})
}
})
- 通过发布订阅模式封装
eventbus
,适用于先注册监听函数再触发事件。
// utils/eventBus.js
const EventPool = {};
const eventBus = {
$on: function (eventName, cb) {
let cbList = EventPool[eventName] || [];
cbList.push(cb);
EventPool[eventName] = cbList;
},
// 先监听,再触发
$emit: function (eventName, param) {
if (EventPool[eventName]) {
EventPool[eventName].map((cb) => {
if (typeof cb== "function") {
cb(param);
}
});
}
},
// 删除事件的某个回调监听,或者删除事件所有的监听
$off: function (eventName, cb) {
if (cb && typeof cb === "function") {
let index = EventPool[eventName].findIndex((item) => item === cb);
if (index!=-1) {
EventPool[eventName].splice(index, 1);
if (!EventPool[eventName].length) {
delete EventPool[eventName];
}
}
} else {
delete EventPool[eventName];
}
},
};
module.exports = {
eventBus,
};
//app.js
const eventBus = require("utils/event-bus.js");
APP({eventBus})
// demo1.js
const app = getApp();
onLoad(){
app.eventBus.$on("eventName", this.foo);
},
onUnload(){
app.eventBus.$off("eventName", this.foo);
}
methods:{
foo(){
// do something
}
}
// demo2.js
app.eventBus.post("eventName",payload);
2、Vue2、Vue3
props/emit
、Vuex
、pinia
、eventBus
、provide/inject
、$attrs/$listeners
网上有很多写的很好的详细的文章,大家可以搜来看。
在组件外使用pinia
3、React
props
、redux
十二、模块化
1、小程序
使用module.exports
、require
小程序目前不支持直接引入
node_modules
, 开发者需要使用到node_modules
时候建议拷贝出相关的代码到小程序的目录中,或者使用小程序支持的 npm 功能。
module.exports = {
eventBus
}
const eventBus = require('utils/eventBus.js')
2、Vue2、Vue3
使用ESModule
:export
、 import
3、React
使用ESModule
:export
、 import
十三、请求代理
1、小程序
不需要配置请求代理,需要在小程序公众平台(开发管理-开发配置)中配置服务器域名。
不支持promise
风格调用,可自行封装。
//env.js 配置环境变量,自测、生产发版时手动切换环境。
const env = {
test: {
baseUrl: "https://xxx",
},
pro: {
baseUrl: "https://xxx",
},
};
module.exports = {
...env.test,
// ...env.pro
};
//request.js
const env = require("../env.js");
options.url = env.baseUrl + options.url;
let header = {};
header['token'] = xxx
return new Promise((resolve, reject) => {
wx.request(Object.assign({}, options, {
header,
success(res){ resolve(res.data) },
fail(err){ reject(error) },
}))
})
2、Vue2、Vue3
如果后端接口发布在多个域名,可配置多个代理。
// vue.config.js
proxy: {
'/apis': {
target: process.env.VUE_APP_BASEURL,
ws: true,
changOrigin: true,
pathRewrite: {
'^/apis': ''
}
},
}
十四、插槽
1、小程序
默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
2、Vue2、Vue3
v-slot:name='slotProps'
指令,作用域插槽
<parent-element>
<template v-slot:header='slotProps'>
something replace slot header {{slotProps.xx}}
</template>
<template #footer>
something replace slot footer
</template>
something replace slot default
</parent-element>
<child-element>
<slot name='header' :xx='data'>something backup header</slot>
<slot name='footer'>something backup footer</slot>
<slot>something backup default</slot>
</child-element>
3、React
没有插槽概念,{}
可传递React
元素,子组件通过props
获取。
const leftJsx = <span>aaa</span>;
<NavBar
leftSlot={leftJsx}
centerSlot={<strong>bbb</strong>}
rightSlot={<a href="/#">ccc</a>}
/>
// 子组件
const { leftSlot, centerSlot, rightSlot } = props
<div>{leftSlot}</div>
以上就是目前全部的内容了,React我目前还是刚上手阶段,只写了我会的,不是很完整。其他的如有错误或不当,欢迎指正、讨论。
终于写完了😀。