0
点赞
收藏
分享

微信扫一扫

项目实战【vue,react,微信小程序】(1707B)


目录

​​一、transition过渡动画​​

​​二、比较两个对象是否相等​​

​​三、图片懒加载使用gif图做loading​​

​​四、图片懒加载使用动画做loading​​

​​五、forEach​​

​​六、vuex getters​​

​​七、详情页对象变更检查注意事项​​

​​八、对话框组件​​

​​九、自定义alert,全局函数使用​​

​​十、瀑布流​​

​​十一、reduce和immutable.js​​

​​十二、使用immutable实现对象深拷贝​​

​​十三、路由懒加载​​

​​十四、小程序自定义对话框组件​​

​​十五、小程序案例​​

​​github源代码:​​


一、transition过渡动画

项目实战【vue,react,微信小程序】(1707B)_html

过渡:

<template>
<div>
<div>
<router-link to="/index/home" class="m-nav-item">首页</router-link>
<router-link to="/index/my_book" class="m-nav-item">书包</router-link>
<router-link to="/index/news" class="m-nav-item">新闻</router-link>
</div>
<transition name="m-slide">
<router-view class="m-router"></router-view>
</transition>
</div>
</template>

<script>
export default {
watch: {
$route(to, from) {
console.log(to);
}
},
methods: {

}
};
</script>

<style>
.m-router {
position: absolute;
width: 100%;
}
/* 新路由的初始样式 */
.m-slide-enter {
transform: scale(2, 2) rotate(-5deg) translate(100%, 0);
opacity: 0;
}

/* 新路由的目标样式 */
.m-slide-enter-to {
color: red!important;
}

/* 新路由动画 */
.m-slide-enter-active {
transition: all 1s ease-in-out;
}

/* 老路由目标样式 */
.m-slide-leave-to {
transform: translate(-100%, 0);
}

/* 老路由动画 */
.m-slide-leave-active {
transition: all 1s ease-in-out;
}
</style>

动画:

<template>
<div>
<div>
<router-link to="/index/home" class="m-nav-item">首页</router-link>
<router-link to="/index/my_book" class="m-nav-item">书包</router-link>
<router-link to="/index/news" class="m-nav-item">新闻</router-link>
</div>
<transition name="m-slide">
<router-view class="m-router"></router-view>
</transition>
</div>
</template>

<script>
export default {
watch: {
$route(to, from) {
console.log(to);
}
},
methods: {

}
};
</script>

<style>
.m-router {
position: absolute;
width: 100%;
}

/* 新路由动画 */
.m-slide-enter-active {
animation: new-router 1s ease-in-out;
}

/* 老路由动画 */
.m-slide-leave-active {
animation: old-router 1s ease-in-out;
}

@keyframes new-router {
0% {
transform: scale(2, 2) rotate(-5deg) translate(100%, 0);
opacity: 0;
}
50% {
color: red;
}
100% {

}
}

@keyframes old-router {
0% {
transform: translate(100px)
}
100% {
transform: translate(-100%, 0);
}
}
</style>

二、比较两个对象是否相等

使用immutable:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>immutable.js实例</title>
<style type="text/css">
</style>
</head>

<body>
<script src="https://cdn.bootcss.com/immutable/4.0.0-rc.12/immutable.js"></script>
<script type="text/javascript">
// 原来的写法
var foo = { a: { b: 1 } };
var bar = foo;
bar.a.b = 2;
console.log(foo.a.b); // 打印 2
console.log(foo === bar); // 打印 true

// 使用 immutable.js 后
var foo = Immutable.fromJS({ a: { b: 1 } });
var bar = foo.setIn(['a', 'b'], 2); // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b'])); // 使用 getIn 取值,打印 1
console.log(foo === bar); // 打印 false
console.log(bar.getIn(['a', 'b'])) //2
console.log(bar.toJS()) //转变成js对象

let obj1 = Immutable.fromJS({
a: {
b: 1,
c: [1, 2]
}
})

let obj2 = Immutable.fromJS({
a: {
b: 1,
c: [1, 2]
}
})

//比较两个对象是否相等
console.log(Immutable.is(obj1, obj2))
</script>
</body>

</html>

 使用原生js(核心代码):

function compare2Objects(x, y) {
for (let p in x) {
// if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
// return false;
// } else if (typeof y[p] !== typeof x[p]) {
// return false;
// }

switch (typeof (x[p])) {
case 'object':
if (!compare2Objects(x[p], y[p])) {
return false;
}
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}

return true;
}

let obj3 = {
a: {
b: 1,
c: [1, 2]
}
}

let obj4 = {
a: {
b: 1,
c: [1, 3]
}
}

console.log(compare2Objects(obj3, obj4))


详细代码请参考:

​​https://www.jianshu.com/p/90ed8b728975​​

三、图片懒加载使用gif图做loading

项目实战【vue,react,微信小程序】(1707B)_懒加载_02

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import lazyload from 'vue-lazyload'
import './index.css'
import loading from './images/loading.gif'

Vue.use(lazyload, {
loading: loading
})

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
<div v-for="item in list" :key="item.id" class="m-list-item">
<div class="m-img-wrap">
<img v-lazy="item.avatar" class="m-img">
</div>
<div class="m-info">
{{item.title}}
</div>
</div>
.m-list-item{display: flex;margin: 5px;}
.m-img-wrap{display: flex; width: 122px;height: 150px;background: #dddddd;}
.m-img{width: 122px;height: 150px;}
.m-img[lazy=loading]{margin: auto; width: 38px;height: 38px;}
.m-info{flex:1}

四、图片懒加载使用动画做loading

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import lazyload from 'vue-lazyload'
import './index.css'


Vue.use(lazyload)

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
<div v-for="item in list" :key="item.id" class="m-list-item">
<div class="m-img-wrap">
<img v-lazy="item.avatar" class="m-img">
</div>
<div class="m-info">
{{item.title}}
</div>
</div>
.m-list-item{display: flex;margin: 5px;}
.m-img-wrap{display: flex; width: 122px;height: 150px;background: #dddddd;}
.m-img-wrap::before{content: '';margin: auto;width: 38px;height: 38px;background-image: url(./images/loading.png);animation: loading 1s linear infinite;}
.m-img{position: absolute; width: 122px;height: 150px;}
.m-img[lazy=loading]{margin: auto; width: 38px;height: 38px;}
.m-info{flex:1}

@keyframes loading {
0% {transform: rotate(0);}
100%{transform: rotate(360deg);}
}

五、forEach

在forEach中,不能使用 continue 和 break ,可以使用 return 或 return false 跳出循环,效果与 for 中 continue 一样。注意该方法无法一次结束所有循环,需要一次性结束所有循环,还是老老实实使用for方法。

var arr = ['a', 'b', 'c', 'd', 'e'];
arr.forEach(function(item, index) {
if (item === 'b') {
return true; //相当于continue
}
if (item === 'c') {
return false; //相当于continue
}
console.log(index + ':' + item);
});

项目实战【vue,react,微信小程序】(1707B)_ide_03

六、vuex getters

通过属性访问:

getters: {
getCurrentList(state) {
return state.currentList
}
},
export default {
computed: {
list() {
return this.$store.getters.getCurrentList
}
}
}

通过方法访问:

getters: {
getTaskList(state) {
return (type, search) => {
return state.taskList.filter(item => {
if (type) {
return item.type === type
} else {
return true
}
}).filter(item => {
if (search) {
return item.name.includes(search)
} else {
return true
}
})
}
}
},
methods: {
handleTask(type) {
this.taskList = this.$store.getters.getTaskList(type, this.search)
this.type = type
console.log(this.taskList)
},
handleSearch() {
this.taskList = this.$store.getters.getTaskList(this.type, this.search)
console.log(this.taskList)
}
},

七、详情页对象变更检查注意事项


<template>
<div>
<img :src="detail.avatar" />
<div>
{{detail.summary}}
</div>
<div>
<button v-if="detail.is_in_my_book">已收藏</button>
<button v-else @click="handleAdd(detail)">收藏</button>
</div>
</div>
</template>

<script>
import Api from '../api'

export default {
data() {
return {
detail: {}
};
},
methods: {
handleAdd(detail) {
detail.count = 1
Api.add({
book: detail
}).then(res => {
if(res.code === 200) {
//this.detail.is_in_my_book = true //不推荐的写法
//this.$set(this.detail, 'is_in_my_book', true) //推荐的写法
//this.detail = {...this.detail, is_in_my_book: true} //推荐的写法
this.detail = Object.assign({}, this.detail, {is_in_my_book: true}) //推荐的写法
}
})
}
},
mounted() {
let { id } = this.$route.params;
Api.getDetail(`?id=${id}`).then(res => {
if (res.code === 200) {
this.detail = res.data;
}
});
}
};
</script>

<style>
</style>

参考链接:

​​https://cn.vuejs.org/v2/guide/list.html#%E5%AF%B9%E8%B1%A1%E5%8F%98%E6%9B%B4%E6%A3%80%E6%B5%8B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9​​


八、对话框组件

Dialog.vue:

<template>
<div class="m-dialog-wrap" :class="{active: visible}">
<div class="m-dialog">
<div class="m-dialog-title">{{title}}</div>
<div class="m-dialog-content">
<slot></slot>
</div>
<div class="m-dialog-footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</template>

<script>
export default {
props: ['visible', 'title']
}
</script>

<style>

</style>

css:

.m-dialog-wrap{display: none;position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.2);}
.m-dialog-wrap.active{display: flex;}
.m-dialog{display: flex;flex-direction: column; margin: auto;padding: 10px; min-width: 200px;min-height: 150px; background: #ffffff;border-radius: 5px;}
.m-dialog-title{line-height: 50px;font-weight: bold;}
.m-dialog-content{flex: 1;}
.m-dialog-footer{line-height: 50px; text-align: right;}

使用:

<Dialog :visible="visible" title="提示">
<div>你确定要删除选中的图书吗?</div>
<div>...</div>
<template v-slot:footer>
<div>
<button @click="handleHideDialog">取消</button>
<button @click="handleDeleteSelected">确定</button>
</div>
</template>
</Dialog>


九、自定义alert,全局函数使用

参考链接:

​​https://www.jb51.net/article/159958.htm​​

vue.extend():

项目实战【vue,react,微信小程序】(1707B)_懒加载_04

​​https://cn.vuejs.org/v2/api/#Vue-extend​​


Vue.extend()  参数是一个vue组件,使用脚手架开发时即.vue的文件,返回值是一个构造函数,这个构造函数使用new创建一个组件实例

项目实战【vue,react,微信小程序】(1707B)_ide_05

项目实战【vue,react,微信小程序】(1707B)_ide_06


Alert.vue:

<template>
<div class="m-dialog-wrap" :class="{active: visible}">
<div class="m-dialog">
<div class="m-dialog-title">{{title}}</div>
<div class="m-dialog-content">
{{message}}
</div>
<div class="m-dialog-footer">
<button @click="handleHideDialog">确定</button>
</div>
</div>
</div>
</template>

<script>
export default {
data() {
return {
title: '',
message: '',
visible: true
}
},
methods: {
handleHideDialog() {
this.visible = false
}
}
}
</script>

<style>

</style>

index.js:

import Vue from 'vue'
import Alert from './Alert'

const AlertConstructor = Vue.extend(Alert)

const AlertFun = (options) => {
let instance = new AlertConstructor({data: options}).$mount()
document.body.appendChild(instance.$el)
}

export default AlertFun

vue入口函数在原型链上添加函数:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import Alert from './components/Alert/index.js'

Vue.prototype.$MyAlert = Alert

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

在组件里使用:

this.$MyAlert({title: '标题', message: '请选择要删除的图书~'})

在拦截器里使用:

import MyAlert from '../components/Alert'

axios.defaults.baseURL = 'http://localhost:85'

axios.interceptors.response.use((res) => {
if (res.data.code === 400) {
MyAlert({title: '标题', message: res.data.message})
}
return res
})

十、瀑布流

项目实战【vue,react,微信小程序】(1707B)_懒加载_07

Waterfall.vue:

<template>
<div class="m-news-wrap">
<div class="m-news-serarch">
<input type="text" placeholder="请输入关键词" v-model="search" @keyup.enter="handleSearch" />
</div>
<div class="m-waterfall js-waterfall" @scroll="handleScroll">
<div id="m-waterfall-top"></div>
<div>
<div v-for="item in list" :key="item.id" class="m-waterfall-item-wrap js-waterfall-item-wrap">
<div class="m-waterfall-item">
<img :src="item.image" class="m-waterfall-img" :height="imageHeight(item.image)" />
<div class="m-waterfall-info">{{item.id}} {{item.name}}</div>
</div>
</div>
</div>
<div class="m-waterfall-end">{{end}}</div>
</div>
<el-backtop target=".js-waterfall"></el-backtop>
</div>
</template>

<script>
import Api from "../api";

let isUpdated = true;
export default {
data() {
return {
list: [],
page: 1,
search: "",
end: ""
};
},
methods: {
handleScroll(e) {
console.log(
e.target.clientHeight,
e.target.scrollTop,
e.target.scrollHeight
);
let { scrollTop, clientHeight, scrollHeight } = e.target;
if (
scrollTop + clientHeight + 200 > scrollHeight &&
isUpdated &&
this.end === ""
) {
this.page++;
isUpdated = false;
Api.news(`?page=${this.page}&size=10&search=${this.search}`).then(
res => {
if (res.code === 200) {
this.list = [...this.list, ...res.data];
if (res.data.length < 10) {
this.end = "我是有底线的~";
}
}
}
);
}
},
handleSearch() {
document.getElementById("m-waterfall-top").scrollIntoView(true);
Api.news(`?page=1&size=10&search=${this.search}`).then(res => {
if (res.code === 200) {
this.list = res.data;
this.page = 1;
this.end = "";
}
});
},
waterFall() {
let items = document.getElementsByClassName("js-waterfall-item-wrap");
items = Array.from(items);
items[0].style.top = 0; //第一个元素
items[0].style.left = 0;
items[1].style.top = 0; //第二个元素
items[1].style.left = "50%";

let arr = [];
arr.push(items[0].offsetHeight); //第一个元素
arr.push(items[1].offsetHeight); //第二个元素
for (let i = 2; i < items.length; i++) {
let minHeight = arr[0];
let index = 0;
for (let j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j];
index = j;
}
}
items[i].style.top = arr[index] + "px";
items[i].style.left = items[index].offsetLeft + "px";
arr[index] = arr[index] + items[i].offsetHeight;
}
},
imageHeight(url) {
let size = url.slice(url.lastIndexOf("/") + 1).split("x");
//真实宽度 / 真实高度 = 原始宽度 / 原始高度
//真实高度 = 真实宽度 / 原始宽度 * 原始高度
let height =
((window.document.body.clientWidth / 2 - 20) / size[0]) * size[1];
return height;
}
},
updated() {
isUpdated = true;
this.waterFall();
},
mounted() {
Api.news("?page=1&size=10").then(res => {
if (res.code === 200) {
this.list = res.data;
}
});
}
};
</script>

<style>
</style>

css:

.m-waterfall{position: relative; flex:1; overflow-y: auto;overflow-x: hidden; background: #eeeeee;}
.m-waterfall-item-wrap{display: inline-block;position: absolute; width: 50%;padding: 10px;box-sizing: border-box;vertical-align: top;}
.m-waterfall-item-wrap:last-child{margin: 0 0 50px 0;}
.m-waterfall-item{border-radius: 10px;}
.m-waterfall-img{width: 100%; border-top-left-radius: 10px;border-top-right-radius: 10px;}
.m-waterfall-info{margin: -4px 0 0;padding: 5px; height: 100px;background: #ffffff;border-bottom-right-radius: 10px;border-bottom-left-radius: 10px;}
.m-waterfall-end{position: fixed;width: 100%;line-height: 50px; bottom: 0; text-align: center;color: #aaaaaa;}

mock数据:

const news = Mock.mock({
'list|500': [{
'id|+1': 1,
'name': '@cname',
//'image': Mock.Random.image(null, '#ff0000', '#ffff00', 'hello'),
'image': '@image()',
'title': '@ctitle',
'paragraph': '@cparagraph',
'datetime': '@datetime'
}]
}).list

分页+搜索接口:

app.get('/api/news', (req, res) => {
let { page, size, search = '' } = req.query
let newsSearchResult = news.filter(item => item.name.includes(search))
let start = (page - 1) * size
let end = start + size * 1
res.send({
code: 200,
data: newsSearchResult.slice(start, end),
message: '新闻'
})
})

十一、reduce和immutable.js

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了​​Structural Sharing(结构共享)​​,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

项目实战【vue,react,微信小程序】(1707B)_ide_08

1. 降低 Mutable 带来的复杂度

2. 节省内存空间

3. Undo/Redo,Copy/Paste,随意穿越!

4. 拥抱函数式编程

不要修改 state:

​​https://www.redux.org.cn/docs/basics/Reducers.html​​

immutable官网:

​​https://immutable-js.github.io/immutable-js/​​

为啥要使用immutable:

​​http://yunlaiwu.github.io/blog/2016/12/01/react+redux+immutablejs/#2​​

简介:

Facebook 工程师 Lee Byron 花费 3 年时间打造!

​​https://www.jianshu.com/p/0fa8c7456c15​​


import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { fromJS } from 'immutable'

const defaultState = fromJS({
listAll: [],
currentId: 0,
myBook: []
})

const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'SET_STATE':
// let newState = JSON.parse(JSON.stringify(state))
// newState[action.key] = action.value
//state是一个immutable对象,调用setIn方法并不会修改state的值,因为state是immutable对象
//newState是基于state和action创建出来的新immutable对象,这个创建过程并不是深拷贝,深拷贝性能很低
//这个创建过程会共享没有改变的部分,避免深拷贝把所有节点都复制一遍带来的性能损耗
//对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享
let newState = state.setIn(action.key, fromJS(action.value))
console.log(state.toJS())
console.log(newState.toJS())
return newState
default:
return state
}
}

const store = createStore(reducer, applyMiddleware(thunk))

store.subscribe(() => {
//console.log(store.getState().toJS())
})

export default store
const mapStateToProps = (state) => {
state = state.toJS()
return {
navList: state.navList,
currentId: state.currentId,
currentList: state.currentList
}
}

const mapDispatchToProps = (dispatch) => {
return ({
setState(key, value) {
dispatch({ type: 'SET_STATE', key, value })
}
})
}

export default connect(mapStateToProps, mapDispatchToProps)(List)

十二、使用immutable实现对象深拷贝

<!DOCTYPE html>
<html>

<head>
</head>

<body>
<div>
<script src="https://cdn.bootcss.com/immutable/4.0.0-rc.12/immutable.js"></script>
<script>
let obj = {
a: 1,
b: {
c: '1'
},
fun() {
console.log(1)
},
time: new Date(),
d: undefined
}
//有兼容性问题,
let objClone = JSON.parse(JSON.stringify(obj))
console.log(objClone)
//使用immutable可以深拷贝值为undefined、函数和日期的key
let objClone2 = Immutable.fromJS(obj).toJS()
console.log(objClone2)
objClone2.a = 2
console.log(obj.a) //1
console.log(objClone2.a) //2


</script>
</div>


</body>

</html>

项目实战【vue,react,微信小程序】(1707B)_html_09



十三、路由懒加载

import React, { Component, Suspense, lazy } from 'react'
import { Switch, Route } from 'react-router-dom'
import Header from '../components/Header'
import Footer from '../components/Footer'
import Home from './Home'
//import MyBook from './MyBook' //无懒加载
import Me from './Me'
//const MyBook = lazy(() => import('./MyBook')) //有懒加载
import Loading from '../components/Loading'

//懒加载延时1
// const MyBook = lazy(async () => {
// return await new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve(import('./MyBook'))
// }, 500)
// })
// })

//懒加载延时2
const MyBook = lazy(async () => {
let component
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(import('./MyBook'))
}, 500)
}).then(res => {
component = res
})

return component
})

export default class Index extends Component {
render() {
return (
<div className="m-wrap">
<Header></Header>
<Suspense fallback={<div className="m-main"><Loading lazyLoading={true}></Loading></div>}>
<Switch>
<Route path="/index/home" component={Home}></Route>
<Route path="/index/my_book" component={MyBook}></Route>
<Route path="/index/me" component={Me}></Route>
</Switch>
</Suspense>
<Footer></Footer>
<Loading></Loading>
</div>
)
}
}

十四、小程序自定义对话框组件

dialog.js:

// components/dialog/dialog.js
Component({
options: {
styleIsolation: 'apply-shared', //isolatiion apply-shared shared
multipleSlots: true
},
/**
* 组件的属性列表
*/
properties: {
visible: Boolean,
title: String
},

/**
* 组件的初始数据
*/
data: {

},

/**
* 组件的方法列表
*/
methods: {
//小程序阻止遮罩层下的页面滚动
handleMove(e) {

}
}
})

dialog.wxml:

除 ​​bind​​​ 外,也可以用 ​​catch​​​ 来绑定事件。与 ​​bind​​​ 不同, ​​catch​​ 会阻止事件向上冒泡。

<!--components/dialog/dialog.wxml-->
<view class="m-dialog-wrap {{visible ? 'active' : ''}}" catch:touchmove="handleMove">
<view class="m-dialog">
<view class="m-dialog-header">{{title}}</view>
<view class="m-dialog-content">
<slot name="content"></slot>
</view>
<view class="m-dialog-footer">
<slot name="footer"></slot>
</view>
</view>
</view>

dialog.wxss:

/* components/dialog/dialog.wxss */
.m-dialog-wrap{display: none; position: fixed;top: 0;left: 0;right: 0;bottom: 0;background: rgba(0, 0, 0, 0.5);z-index: 99;}
.m-dialog-wrap.active{display: flex;}
.m-dialog{display: flex;flex-direction: column; margin: auto;padding: 10rpx; min-width: 600rpx;min-height: 400rpx;background: #ffffff;border-radius: 10rpx;}
.m-dialog-header{height: 80rpx;font-weight: bold;}
.m-dialog-content{flex: 1;}
.m-dialog-footer{height: 80rpx;text-align: right;}

使用dialog组件:

<dialog visible="{{visible}}" title="添加">
<view slot="content" class="m-count-wrap">
<view class="m-count">
<text class="m-text-btn" bind:tap="handleSub">-</text>
<input class="m-input" placeholder="请输入" value="{{item.count}}"></input>
<text class="m-text-btn" bind:tap="handleAdd">+</text>
</view>
</view>
<view slot="footer">
<button size="mini" class="m-btn" bind:tap="handleHideDialog">取消</button>
<button size="mini" class="m-btn" bind:tap="handleAddToMyBook">确定</button>
</view>
</dialog>


十五、小程序案例

项目实战【vue,react,微信小程序】(1707B)_懒加载_10





github源代码:

​​https://github.com/baweireact/m-apps/tree/master/m-app-1707B​​













举报

相关推荐

0 条评论