超好吃的小奶枣,非常强烈推荐的休闲零食~
打一个小小的广告哈,是老姐的闺蜜姐姐做的,真的好吃,奶味道很足,也很脆。我本来是不吃枣的,但是被包在这个点心里面真的无敌绝。
想买的快加微信了解一下oh~~
打一个小小的广告哈,是老姐的闺蜜姐姐做的,真的好吃,奶味道很足,也很脆。我本来是不吃枣的,但是被包在这个点心里面真的无敌绝。
想买的快加微信了解一下oh~~
最近一直在调研unicloud云函数开发,所以一直想给js加入类型推导,这篇文章就记录一下我是如何开发TS版本的云函数的吧。
Flow.js在基本语法上和TS很相像,我认为它是一个针对老项目的类型推导方案,因为只需要安装简单的包和给文件加入Flow的标识就可以给对应的文件提供类型推导的功能,所以针对目前我所做的这个业务来讲,不存在老项目,所以既然是新项目就不如直接上TS。
在云函数开发过程中,我们在插件市场选择了一款非常简洁已拓展的explain框架,这个框架目前已经支持单路由和restfulAPI还有基本的过滤拦截器,那么目前这个框架没有做TS的解决方案,我就斗胆替作者大大想一个曲线救国的方案,而且这个方案有以下特点:
uni官方的云函数大小限制是10M,所以我们不能把依赖都安装在项目中,需要我们全局安装:
npm i -g typescript esbuild-node-tsc nodemon
2个插件的玩法很多,尤其是nodemon,在我们这个解决方案中我们只需要简单的配置几个文件就可以把我们的项目跑起来了。
我们的云函数目录是这样的,这是搭配了前面提到的explain.js,在etsc.config.js中我们可以配置一下,输出的js版本规范以及目录和是否进行压缩:
module.exports = {
outDir: "./dist",
esbuild: {
minify: true,
target: "es2015",
},
assets: {
baseDir: "services",
filePatterns: ["**/*.json"],
},
};
在services目录中编写完ts文件之后,esbuild-node-tsc会把js文件放到dist目录之下,我们现在只需要更改explain.js默认配置:
config.init({
baseDir: __dirname,
serviceDir: "/dist/"
});
这样explain会从dist下找文件而不是从services文件下找
我们在这个根目录下运行编译命令即可
etsc
这个时候我们运行这个函数就会发现,它已经达到了我们的目标了:
但是我们需要services下的文件一变更就编译放到dist下,我们就需要nodemon帮助我们做这个事情(nodemon.json):
{
"watch": ["services"],
"ext": "ts,js,json",
"exec": "etsc",
"legacyWatch": true
}
监听services目录,包括文件名为ts,js,json,执行命令etsc
然后我们再把这个运行nodemon的命令放到package.json中:
"scripts": {
"dev": "nodemon"
},
这样我们就可以启动nodemon这个监听服务,可以很爽的使用ts来开发云函数啦~~
vite这个工具确实尤大在微博上造势很猛,又以各种骚操作着实火了一把,那我们今天就一起了解一下vite吧~
我们可以从vite仓库的readme可以看到安装vite非常的方便
// https://github.com/vitejs/vite
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
我们在本地环境运行之后可以看到这样的页面,就说明我们可以开始使用了。
我们首先要理解vite的工作原理,它为什么这么快?
当我们打开工程的index.html的时候,我们可以发现script的type是module
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
如果你不了解script标签中的module是什么意思,那么MDN解释说如果标示了module的话会把代码当作js模块来执行,一篇关于es6的文章也很好的介绍了:
https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script
如果你熟悉es6的模块概念,模块仅仅就是普通的含有js代码的文件而已,我们可以用import和export关键字对变量,对象的导出和导入,而这个机制在高级浏览器已经完全实现了。
不得不说,尤大不仅是技术上的大神,而且富有创造和想象力。让vite变成了一个0捆绑的开发服务器,利用浏览器高级特性让开发体验变得更好
当浏览器解析保留了模块关键字的代码,从而会导致HTTP请求,vite通过koa拦截了这些请求:
.vue => 拦截请求 => 编译 => 返回给客户端
那么没有打包的vite和老东家的基于打包的vue-cli就有了一些明显的优势了:
1. vite利用了客户端能力不用打包其他服务,原生的ES Import直接输出提高了冷启动速度。
2. vite按需编译当前页面需要的组件,而不需要打包整个APP的组件,这样的提升对比cli无疑是项目越大速度差距越大。
3. HMR更新速度不会和模块数量牵扯,vite会让HMR一如既往的保持快速。
使用vite开发应用可能在前期除了启动速度,其他功能是要等到应用慢慢变大才能真实的感受vite的强大。
TS支持
vite内置TS的支持,开箱即用:
<script lang="ts">
import { ref } from "vue";
export default {
name: 'App',
setup(){
let hello = ref<string>("1");
console.log(hello.value);
}
}
</script>
值得一提的是,内置的TS不是TS官方出的tsc cli,而是之前就听说过的ESBuild,现在vite的TS支持是ESBuild也不奇怪,毕竟是要一快到底么。
——ESBuild的ReadMe说了一句振奋人心的话
实际上,我们目前的网站构建工具比实际速度慢10-100倍
为什么ESbuild这么快?
虽然ESbuild是一个非常快速的打包器,但是不支持热模块更新和没有开箱即用的工具,而且要像webpack一样想做一款基于ESbuild的插件,我认为目前是非常难的。所以Vite将它的长处用在了处理ts编译上,大型项目中编译TS文件,Vite几乎是一瞬间的事情。
热模块更新
我们要知道热模块更新和我们传统的刷新页面的区别,以webpack的dev-server服务器举例,通过启动开发服务器,页面与服务器建立了websocket,我们修改了代码之后给页面发送消息,页面才会执行刷新命令,本质上这种live-reload机制已经对开发非常友好了,但是在带有状态的页面上,reload不会有更好的开发体验:
当页面存在弹窗或者编辑框等,代码修改之后,liveReload会重载页面,如果刷新代码的同时不会重载页面而是重新加载修改过的文件就完美,所以这个机制就是webpack提出的热替换技术,也就是我们说的热更新
webpack.config.js
module.exports = {
mode: "development", // 开发环境
devtool: "cheap-module-eval-source-map",
devServer: {
contentBase: "./bundle",
open: true,
hot: true, // 开启热模块更新
hotOnly: true // 更新失败不会刷新页面配置
},
module: {
rules: [{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}]
}
}
css的loader中的实现已经做了热更新的处理,通过HMR这个插件中的API
// main.jsif (module.hot) {
module.hot.accept(function() {
// 监听变化,则修改
});
module.hot.dispose(function() {
// 移除 });
}
我们了解了HMR基本操作之后就可以看看Vite是如何做HMR的:
vite和webpack的HMR实现机制是一样的,都是通过客户端和服务端建立socket连接,服务端有变化则通知客户端做出改变:
// server/serverPlugin.ts
watcher.on('change', (file) => {
if (!(file.endsWith('.vue') || isCSSRequest(file))) {
handleJSReload(file);
}
})
在handleJSReload函数中递归调用了walkImportChain这个函数,这个函数的作用就是查看当前变化的文件是谁引入了它(JS/Vue),那么在递归中没有找到谁引入它,就Full-reload
send({
type: 'full-reload',
path: publicPath
})
如果找到了引用这个JS的文件了就热更新:
send({
type: 'multi',
updates: boundaries.map((boundary) => {
return {
type: boundary.endsWith('vue') ? 'vue-reload' : 'js-update',
path: boundary,
changeSrcPath: publicPath,
timestamp
}
})
})
在客户端中,vite则在核心处理函数handleMessage中定义了消息的类型:
客户端接受了不同的消息类型去做不同处理,根据timestamp时间戳去请求新文件,而vue文件则通过HMRRuntime更新。
裸模块导入
vite同样也支持其他家打包器的日常功能,浏览器不允许我们直接引入裸模块,例如:
import { add } from "lodash"
vite在裸模块处理上有着对vue得天独厚的优势,vite不仅仅的可以改写普通模块的路径然后正确的解析,还对vue这个依赖有特殊的处理:
如果项目本地没有安装vue依赖,那么引入vue模块会按照vite依赖的vue版本去执行,这就说明了如果你全局安装了vue,那么在vite项目中能更方便的找到它
vite重写了模块加载路径:
// src/node/server/serverPluginModuleRewrite.ts
ctx.body = rewriteImports(
root,
content!,
importer,
resolver,
ctx.query.t
)
引入的模块上下文在经过rewriteImports方法处理body以后,就会造成这样的效果:
import vue from "vue" => import vue from "@/modules/vue.js"
在rewriteImports这个方法中使用到了Esbuild中的es-module-lexer进行词法分析,对esbuild本来就不熟悉的我去看了这个插件发现这个词法分析器又小,又可以快速对JS进行分析,这里就简单看一下官网的demo吧。
// 伪代码
// 和vite源码中一样
import {
init as initLexer,
parse as parseImports,
ImportSpecifier
} from 'es-module-lexer'
// 需要初始化
await initLexer;
const [import, export] = parseImports(`
import {a} from "a"
export const add = 1;
`
console.log(import[0].s);
// 解析结果的返回有这样几种
// "s" is shorthand for "start"
// "e" is shorthand for "end"
// "ss" is shorthand for "statement start"
// "se" is shorthand for "statement end"
经过这样的词法处理,我们的模块引入路径就被这样替换了,尽管这个插件这么强,对于词法分析这种东西我们开发者平时也用不到,所以大家只需要知道vite的模块路径替换是借助es-module-lexer进行词法分析的就可以啦~
总结
vite和webpack对比,我认为webpack是一个纯正的打包工具,它的生态非常丰富,可以基于插件做各种事情,但是像尤大说的一样,很少有基于webpack上层封装的工具出现,也就是具有很大学习和配置成本,而vite提供了更丝滑的开发体验,以及内置的强大的HMR,TS支持,WebAssembly支持等等,所以使用哪款产品要看业务需要。
由于vite目前还在不断的更新中,但是主要特性的原理应该是不会变的,因为vite有非常多优秀的其他特性,还有我们这篇文章提到的3个特性还有很多值得细细研究的地方,所以这个vite系列会继续做,谢谢大家支持哦
如题所见,这篇文章可不是软文,我们武林中人不能不讲武德的
主要是自己因为工作后续的需要,需要配置更多的屏幕,所以参考了网上很多大佬的桌面,桌面上动不动mbp,switch,好几千的键盘,4k+的电脑屏幕,千把块的屏幕灯。好家伙把我卖了都没那么多钱,作为程序萌新没那么多钱又想打造一个实用的桌面,你就该看我这个!
完成效果图:
首先我们从价格低到高排序咯,首先就要介绍的笔记本夹架:
来源:淘宝
价格:20-30
推荐星级:五颗星
推荐链接:
评价:不用笔记本的朋友可以用这个架子,这个架子本来是夹餐巾纸的,但是它足够稳固,夹得住,立起来不会伤害电脑,再加上外观清爽,非常好!
桌上的老物件:手机/ipad支架是在线下宜家店淘的,这个支架有充电口而且是方便组装的,颜色也是实木风格搭配黑色非常好。
来源:线下宜家
价格:30
推荐星级:4颗星
推荐链接:
SIGFINN 希格芬 手机支架 - 竹子贴面 - IKEA
对于干净的桌面,我就选择了桌下的埋线置物架,首先插板走线都可以从这个架子上走,由于是吸附在桌子下的,所以不会影响到抖腿(滑稽)
来源:拼多多
价格:30+
推荐星级:5颗星(无线桌面非常核心)
推荐链接:
对于程序员群体来说,在夜晚工作是家常便饭,那么屏幕挂灯就是我们的必备品,因为它不占空间光线可调节等特性,可以让我们眼睛很舒服,屏幕挂灯有多个品牌,既然我们推崇的是性价比,我们就不得不提到小米的屏幕灯啦!对比明基千元的屏幕挂灯,小米这款灯可谓是性价比拉满,不仅有非对称光源还自带无线遥控,非常适合无线桌面呢
来源:小米有品
价格:200
推荐星级:5颗星
推荐链接:
对于电脑桌,如果想承受更多的屏幕支架和书籍/空间,那就必须选用1.5m++的桌子,一般的电脑桌都会自带埋线孔,所以电脑桌我就在pdd随便买的,价格基本都是在200元左右,没有必要去选用带储物功能的桌子。
来源:拼多多
价格:200左右(尺寸不一样价格不一样)
推荐星级:3颗星
推荐链接:
Oh,My god终于到重头戏了,对于显示器支架真的有太多选择了,这里我买了2款,一款是双屏幕的支架和一个显示器壁挂支架,因为老aoc显示器不支持壁挂所以就在同一家店买了壁挂支架,显示器支架非常便宜,100多块就能拿下oh~
来源:淘宝
价格:100+
推荐星级:5颗星
推荐链接:
接着就是我们的最后一个拼多多商品了,这个商品放到最后一个肯定是有原因的,因为真的是pdd购物血泪史,但是结果还是非常好的。因为预算不够所以把4k显示器降低为2k,价格也降低了一半,所以我就抱着试一试的心态购买了pdd的2k显示器,价格只需500出头就可以拿下24寸2k显示器。
收到货之后,发现屏幕窄边框很舒服,屏幕没坏点,该有的接口都有,可是就是开不了机器,店家阿你不讲武德阿,怎么给我一个坏的呢?然后经过和客服的调试发现是电源坏了,然后店家寄了一个电源给我,发现显示器可以开机了。本以为能体验一下捡漏的显示器发现和笔记本连接不上,经过调试发现是HDMI接口有问题,没办法,因为是壁挂的所以影响了二次销售不能退货,只能花70块快递让店家维修,维修之后,开机!完事大吉~
pdd的显示器买的虽然一波三折,但是结局还是好的,也希望给大家一个教训。
来源:拼多多
价格:510
推荐星级:1颗星(抛开退换货就是五颗星,还是值得一试的)
推荐链接:
Ok!这次打造无线极简桌面的推荐就结束了,还差无线键盘和鼠标这些大家如果为了无线和极简那就挑着自己喜欢的牌子的无线版本就好了噢~
一个风和日丽的下午,我用了3个小时写了一个Vue3的小应用,这个应用小到不足为奇,但是Vue3的开发体验最值得我吹一波,这个小应用在登录我用了Vuex4,路由管理用到了最新的VueRouter4,而UI框架选择的是Vant3,有赞团队的Vant3最新版也是目前为止Vue3支持度最高的移动UI库,放一个项目预览GIF图
整个的开发体验,我总结了一下:有近似ReactHook的开发体验,又保留了Vue2的原汁原味,同时因为Vue3的本身设计原因,也提升了在react中没有的hook开发体验,所以如同标题,如果开发者拥有reactHook开发经验和vue2开发经验,可谓是“闭着眼睛”都能撸Vue3。
Github地址:https://github.com/1018715564/PlanListForVue3
main.js
新版的vue一改以前的构造函数去挂载,而是把函数风格贯彻到底了,在入口文件中我们使用链式调用,一直use一直爽。
import { createApp } from "vue";
import App from "./App.vue";
import Router from "./router";
import Vuex from "./store";
import Vant from "vant";
import "vant/lib/index.css";
createApp(App).use(Router).use(Vant).use(Vuex).mount("#app");
Router
import { createRouter, createWebHistory } from "vue-router";
import Login from "../pages/Login/Index.vue";
import Index from "../pages/Index/Index.vue";
const routerHistory = createWebHistory();
const Router = createRouter({
history: routerHistory,
routes: [
{
name: "Index",
path: "/",
component: Index
},
{
name: "Login",
path: "/Login",
component: Login
}
]
});
export default Router;
可以看到Router还是保留了vueRouter3的路由配置,但是创建路由的方式改为了createRouter,原先的路由模式也从原来的mode: "history"也改为了通过函数引入:
"history":createWebHistory()
"hash":createWebHashHistory()
"abstract":createMemoryHistory()
Vuex
import Vuex from "vuex";
export default Vuex.createStore({
state: {
user: null,
planList: []
},
mutations: {
// 删除一个计划
deletePlan(state, index) {
state.planList.splice(index, 1);
},
// 新增一个计划
addPlan(state, plan) {
state.planList.push(plan);
},
setUser(state, user) {
state.user = user;
}
},
actions: {},
modules: {}
});
我们通过vuex4中的createStore创建了实例,其中我们定义了一些方法和state,这些我们在登录和添加删除计划时会用到,下来我们再看看如何在页面中使用Vue3的组合API以及路由和状态管理。
登录页面
import { reactive } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { Toast } from "vant";
export default {
name: "App",
setup() {
const store = useStore();
const router = useRouter();
// 用户名和密码
const Form = reactive({
username: "",
password: "",
});
// 登录
function handelLogin() {
store.commit("setUser", {
username: Form.username,
password: Form.password,
});
Toast(`登录成功,你好: ${Form.username}, 请添加你的计划吧~`);
// 跳转到首页
router.push({
path: "/",
});
}
return {
Form,
handelLogin,
handelNavBack,
};
}
我们使用路由以及状态管理需要从2个包中引入对应的hook去调用
调用vuex中的mutation:
store.commit(fnName, args);
路由也沿用了VueRouter3中一些老API,replace, go, back等方法都基本不变
const router = useRouter();
router.push({
path: "/",
});
如果从来没有使用过组合API的开发者,应该需要了解一下前置知识,例如在老版本中,我们定义变量和方法是这样:
data(){
return {
count: 0
}
},
methods: {
back(){
return "It is back"
}
}
{{count}} //在template中这样渲染
我们在vue3中需要把这些东西写到setup这个函数中,包括变量,函数,监听,计算属性,生命周期等等,然后把变量return出去供模板使用,很显然我们在登录时候用了reactive这个API,它可以传入一个普通对象,返回一个响应式的代理,我们用这个对象中的用户名和密码与视图绑定最后调用vuex的方法即可让全局状态管理知道此时 “计划APP” 是有身份进入的。
const Form = reactive({
username: "",
password: "",
});
// 调用vuex登录方法
store.commit("setUser", {
username: Form.username,
password: Form.password,
});
首页
import { ref, reactive, computed, watchEffect } from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { Toast, Dialog } from "vant";
export default {
name: "Index",
setup() {
const store = useStore();
const router = useRouter();
const planList = store.state.planList;
const addPlanForm = reactive({
title: "",
remark: "",
});
// 添加的弹出层
const addPopup = ref(false);
// 计算计划的个数
const planCount = computed(() => store.state.planList.length);
// 如果没登录重定向到登录
if (store.state.user === null) {
Toast("未登录,请先登录");
router.replace("Login");
}
const handelNavBack = () => {
router.go(-1);
};
const handelAddPlan = (e) => {
store.commit("addPlan", {
...e,
id: Math.random().toString(36).substr(2),
date: new Date().toDateString(),
});
Toast("添加计划成功");
addPopup.value = false;
addPlanForm.title = "";
addPlanForm.remark = "";
};
const handelDeletePlan = (index) => {
Dialog.confirm({
title: "提示",
message: "您缺点要删除此计划吗?",
}).then(() => {
store.commit("deletePlan", index);
Toast("删除计划成功");
});
};
return {
handelNavBack,
planList,
addPopup,
addPlanForm,
handelAddPlan,
handelDeletePlan,
planCount,
};
},
};
引入了vuex和router的hook之后,我们导出了vuex和router的实例,从这个实例中我们可以获得到vuex中的state,则可以判断APP是否有登录用户,如果没有登录就重定向到登录页面。
const store = useStore();
if (store.state.user === null) {
Toast("未登录,请先登录");
router.replace("Login");
}
添加计划额外传递id和时间
store.commit("addPlan", {
...e, // Form表单的回调,是计划标题和计划备注
id: Math.random().toString(36).substr(2),
date: new Date().toDateString(),
});
使用refAPI将传入的参数返回其响应式代理
// 控制弹窗显示/隐藏的变量
const addPopup = ref(false);
如果传入的是一个对象,那么Vue内部自动会调用reactive,值得注意的是如果需要更改此响应变量,需要对响应式对象中的value属性进行更改。
响应式变量在template渲染时我们不需要写.value
计划APP的全部代码都已经梳理完毕,我们可以在这个APP中学到,状态管理,路由,组合常用的API的简单应用,在组合API中还有一些我们以前经常用到的API,比如计算属性,生命周期,watch等等在组合API拓展阅读中有简单的总结。
组合API拓展阅读
计算属性在vue3中非常简单和vue2中如出一辙,在新版本中计算属性需要从vue引入:
import { computed } from "vue"
setup(){
const planList = computed(() => state.list);
}
计算属性默认传入一个get函数,当然可以像以前一样传入一个set函数
const planList = computed({
get(){
return state.list
},
set(list){
state.list = list
}
})
planList.value = []; // 重新set值
这个Hook非常简单,如果使用过reactHook的useEffect应该非常好理解,如果这个函数所依赖的内容变化,它会重新执行这个函数。
const countEffect = watchEffect(() => {
console.log(count); // 定义的响应式变量,如果该变量有变更,将会重新打印count
});
当组件卸载或者调用返回中的stop方法即可停止监听,这个机制和vue2中的watch是一样滴。
watchEffect我们称之为副作用函数,我们的业务场景中如果有好友列表,鼠标移动上去能异步获取好友详情,那么如果在鼠标在移动新好友之后,上一次好友详情的请求还没结束,这可能会造成数据混淆的Bug,所以我们副作用函数支持我们这样清除副作用:
watchEffect((onInvalidate) => {
const detail = getFriendDetail(id.value)
onInvalidate(() => {
// id 改变时 或 停止侦听时
detail.cancel() // 取消请求
})
})
第一遍看文档时感觉这样清除副作用很麻烦,React是这样清除的:
useEffect(() => {
// do something
return () => {
// 清除副作用
}
})
Vue设计副作用返回是通过传入一个回调注册清除函数,是因为使用effect钩子往往是异步请求,而异步请求返回的是Promise,所以清除函数一定要在被resolve之前注册,当然文档还说这样的好处还有可以自动帮助我们处理Promise潜在的错误(这个就是后话了嘻嘻)。
下面就是重头戏了,大胆预测一波,未来关于Vue3面试题必将有它的一席之地:watchEffect的刷新时机是什么?
第一次看文档就有猜测过这样傻白甜的问题,副作用监听了依赖它的变量,是如何很好的控制多个变量触发的机制呢,是每个变量触发都会快速的执行一次吗?
用户自定义的副作用函数会在全局缓存一遍会异步地刷新它们,Vue组件的更新函数也是一个副作用函数,刷新机制是在更新函数之后去一遍一遍走自定义的副作用函数
watch和Vue2一样,只不过和watchEffect的区别就是:
1. 仅在依赖数据源变化才会回调副作用函数
2. 可以访问到变化前和变化后的值
3. 可以自由设置哪些值是需要监听的
共同点就是:
1. 清除副作用和停止监听
2. 副作用刷新机制也是一样的哟
const star = ref(0);
watch(star, (star, prevStar) => {
// do something
});
// 监听多个
watch([star, rose, flower], ([star, rose, flower], [prevstar, prevrose, prevflower]) => {
// do something
});
结语
这段时间拖得太久,Vue系列还有一个Vite的浅析还没发,也基本差亿点就结束了,因为是国庆节写的,也希望大家多多支持呀,冲鸭!