事务处理 重要 事务处理解释 事务处理是一种将多个操作作为一个单独的、原子性的操作执行的机制。在事务处理中，多个操作被组合成一个单元，这个单元被视为单个不可分割的操作。这个单元中的所有操作都必须成功完成，否则整个事务将被回滚，回滚是指将所有操作撤销回到事务开始之前的状态。 事务处理通常应用于需要对多个数据进行修改的场景，如数据库管理系统中。在这种场景下，如果一个操作失败，所有已经完成的操作都必须撤销，以确保数据的一致性和完整性。事务处理通常包括以下四个属性：原子性（Atomicity）：整个事务作为一个单独的、不可分割的操作执行，要么全部成功，要么全部失败。一致性（Consistency）：事务执行前和执行后，系统的状态应该保持一致。隔离性（Isolation）：并发的事务之间应该相互隔离，以防止数据损坏和冲突。持久性（Durability）：一旦事务被提交，它的结果应该是永久性的，即使系统发生故障也应该如此。事务处理是一种重要的机制，它可以帮助保障数据的完整性和一致性，并且可以提高系统的可靠性和可用性。在前端的应用场景某个APP可能需要依次按照用户操作的特定顺序实现分步修改某些数据,假如步骤是3步,如果最后一步失败了,我需要吧第一步操作的结果进行回滚,这里就需要用到事务.在 JavaScript 中，原生的异步函数和 Promise 不支持回滚（rollback）操作，因为这些操作都是异步执行的，无法像关系型数据库一样实现回滚操作。但是，我们可以通过一些技巧来实现简单的回滚操作。例如，在执行事务之前，我们可以备份一些数据，以便在事务失败时恢复数据。如果事务成功，则删除备份数据。如果事务失败，则恢复备份数据以回滚事务。可以用到下面的代码实现事务处理和模拟简单的回滚:function performTransaction(transactionFunctions) { let backupData = null; return new Promise((resolve, reject) => { const results = []; const transactionPromises = transactionFunctions.map(fn => fn()); Promise.all(transactionPromises) .then(data => { results.push(...data); backupData = null; // 删除备份数据 resolve(results); }) .catch(error => { if (backupData) { // 恢复备份数据以回滚事务 // 在实际情况下，你可能需要使用更安全的备份机制，如深拷贝或序列化/反序列化等 console.log('Rolling back transaction...'); // 恢复备份数据 } reject(error); }); }); } function transaction1() { return new Promise((resolve, reject) => { // 备份数据 if (!backupData) { backupData = 'backup'; } setTimeout(() => { console.log('Transaction 1 completed'); resolve(1); }, 1000); }); } function transaction2() { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Transaction 2 completed'); resolve(2); }, 2000); }); } function transaction3() { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Transaction 3 failed'); reject(new Error('Transaction failed')); }, 500); }); } performTransaction([transaction1, transaction2, transaction3]) .then(results => { console.log('All transactions completed successfully'); console.log(results); }) .catch(error => { console.error('Transaction failed', error); });在这个例子中，我们定义了三个简单的事务函数，其中第一个函数会备份数据。如果事务成功，我们会删除备份数据。如果事务失败，则在 reject() 方法中检查是否存在备份数据，并根据需要恢复数据以回滚事务。请注意，这个示例只是演示如何在 JavaScript 中实现简单的事务回滚操作。在实际项目中，你需要使用更严谨和可靠的备份机制来确保数据的完整性和一致性。任务队列 重要假如需求有三个弹窗。需要依次按照指定的顺序弹出来用户同意,必须严格按照顺序弹出哦.解决方案 1可以使用 Promise 链式调用来实现按照指定的顺序弹出弹窗，等待用户同意之后再继续弹出下一个弹窗的需求。具体实现如下：function showPopup(message) { return new Promise((resolve, reject) => { const popup = document.createElement('div'); popup.innerHTML = ` ${message} OK `; document.body.appendChild(popup); const okButton = popup.querySelector('.ok-btn'); okButton.addEventListener('click', () => { popup.remove(); resolve(); }); }); } showPopup('Popup 1') .then(() => showPopup('Popup 2')) .then(() => showPopup('Popup 3')) .then(() => { console.log('All popups have been shown and user agreed'); // 执行用户同意之后的逻辑 }) .catch(error => { console.error(error); });在这个例子中，我们定义了一个 showPopup 函数，它会在页面上创建一个弹窗，显示给定的消息，并等待用户点击“OK”按钮之后才会解析 Promise。我们使用 Promise 的 then() 方法将 showPopup 函数链接起来，按照指定的顺序依次弹出弹窗。最后一个 then() 方法中的回调函数表示所有弹窗都已经弹出并且用户同意了，可以继续执行后续逻辑。如果在任何一个弹窗中用户点击了“取消”或关闭按钮，将会触发 Promise 的 reject() 方法，执行 catch() 方法中的错误处理逻辑。注意，这个例子中只是演示如何依次按照指定顺序弹出弹窗，并等待用户同意之后再弹出下一个弹窗。在实际项目中，你需要根据具体的需求来实现更加复杂的弹窗逻辑。这样确实可以实现需求,但是不够优雅和灵活,最优解应该使用任务队列的方式来解决,实现代码如下:function showPopup(message) { return new Promise((resolve, reject) => { const popup = document.createElement('div'); popup.innerHTML = ` ${message} OK `; document.body.appendChild(popup); const okButton = popup.querySelector('.ok-btn'); okButton.addEventListener('click', () => { popup.remove(); resolve(); }); }); } const popupQueue = ['Popup 1', 'Popup 2', 'Popup 3']; const showNextPopup = () => { const message = popupQueue.shift(); if (message) { showPopup(message).then(() => { showNextPopup(); }); } else { console.log('All popups have been shown and user agreed'); // 执行用户同意之后的逻辑 } }; showNextPopup(); 实际上，使用队列是一种有效的方式来保证弹窗按照指定的顺序弹出，因为队列是一种先进先出（FIFO）的数据结构，它确保元素按照它们被添加的顺序处理。因为浏览器的事件循环（event loop）的机制。当我们使用 Promise 解析来控制队列时，JavaScript 引擎会在事件循环的下一个迭代中执行每个 Promise，这意味着我们可以在 Promise 解析的回调函数中控制下一个 Promise 的执行。因此，使用 Promise 队列是一种可以保证顺序性的方法。在这个例子中，我们使用 Promise 解析来控制弹窗的顺序。在 showNextPopup 函数中，我们首先从 popupQueue 中取出下一个弹窗的消息，然后使用 showPopup 函数创建一个弹窗，并在 Promise 解析的回调函数中调用 showNextPopup 函数来弹出下一个弹窗。这样，即使在多个 Promise 解析的情况下，我们仍然可以确保弹窗的顺序性。
Sequelize 是一个基于 Node.js 的 ORM（Object-Relational Mapping）框架，可以与多种数据库进行交互，如 MySQL、PostgreSQL、SQLite 等。它的使用简单，支持多种数据库操作，并且可以轻松地进行数据迁移。 本篇博文将介绍如何在 Node.js 环境下使用 Sequelize 进行数据库操作，包括创建表、增删改查等基本操作。安装 Sequelize在使用 Sequelize 之前，需要先安装它及其对应的数据库驱动程序。npm install sequelize mysql2其中，mysql2 是 Sequelize 的 MySQL 驱动程序，可以根据实际需求选择对应的驱动程序。连接数据库首先需要创建一个 Sequelize 实例，用于连接数据库。可以通过以下代码进行创建：const Sequelize = require('sequelize'); ​ const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: 'mysql', logging: false, });上述代码中，database、username、password 分别为数据库的名称、用户名和密码。host 表示数据库的主机地址，dialect 则表示使用的数据库类型。定义模型在 Sequelize 中，一个表对应一个模型。定义模型的方式如下：const { DataTypes } = require('sequelize'); ​ const User = sequelize.define('User', { username: { type: DataTypes.STRING, allowNull: false, }, email: { type: DataTypes.STRING, allowNull: false, unique: true, }, password: { type: DataTypes.STRING, allowNull: false, }, });上述代码中，定义了一个名为 User 的模型，包含了三个属性：username、email 和 password。其中，type 表示属性的类型，allowNull 表示该属性是否允许为空，unique 则表示该属性是否唯一。同步数据库在定义完模型后，需要通过以下代码将模型同步到数据库中：sequelize.sync();如果需要将模型同步到指定的数据表中，可以在定义模型时指定表名：const User = sequelize.define('user', {...}, { tableName: 'users' });增删改查创建记录创建记录有两种方式，一种是使用 create 方法，另一种是使用 build 方法，后者只是将要创建的实例对象保存在内存中，需要使用 save 方法将其持久化到数据库中。// 创建一条新记录 const user = await User.create({ name: 'John Doe', email: 'john.doe@example.com' }); ​ // 创建一条新记录，但不保存到数据库中 const user = User.build({ name: 'John Doe', email: 'john.doe@example.com' }); await user.save();查询记录查询记录有多种方式，可以使用 findAll 方法查询所有记录，使用 findOne 方法查询单条记录，使用 findByPk 方法根据主键查询记录，还可以使用 findAll 方法的选项参数进行条件查询、排序、分页等。const users = await User.findAll(); ​ // 根据主键查询记录 const user = await User.findByPk(1); ​ // 条件查询 const users = await User.findAll({ where: { name: 'John Doe' }, order: [ ['created_at', 'DESC'] ], limit: 10, offset: 0 });更新记录使用 update 方法更新记录。// 更新一条记录 await User.update({ name: 'Jane Doe' }, { where: { id: 1 } });删除记录使用 destroy 方法删除记录。// 删除一条记录 await User.destroy({ where: { id: 1 } });创建一个主键为 id 自增的表const { Sequelize, DataTypes } = require('sequelize'); // 创建 Sequelize 实例 const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: 'mysql' }); // 定义数据表模型 const User = sequelize.define('user', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING, allowNull: false }, email: { type: DataTypes.STRING, allowNull: false } }); // 同步数据表结构 sequelize.sync();需要注意的是，调用 sync() 方法只会在数据库中创建不存在的数据表，已存在的数据表不会受到影响。如果需要修改数据表结构，需要使用数据库迁移工具，例如 Sequelize 提供的 sequelize-cli 命令行工具。事务处理机制const { Sequelize, DataTypes } = require('sequelize'); // 创建 Sequelize 实例 const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: 'mysql' }); // 定义数据表模型 const User = sequelize.define('user', { name: { type: DataTypes.STRING, allowNull: false }, email: { type: DataTypes.STRING, allowNull: false }, balance: { type: DataTypes.FLOAT, defaultValue: 0 } }); // 事务处理 sequelize.transaction(async (t) => { // 在事务中创建一个用户 const user = await User.create({ name: 'Alice', email: 'alice@example.com' }, { transaction: t }); // 在事务中修改用户余额 user.balance += 100; await user.save({ transaction: t }); }).then(() => { console.log('Transaction has been committed'); }).catch((err) => { console.error('Transaction has been rolled back', err); }); 在上面的代码中，首先使用 sequelize.transaction() 方法开启一个事务，该方法接受一个回调函数作为参数，回调函数中执行所有需要在事务中处理的操作。在回调函数中，首先使用 User.create() 方法创建一个用户，并且将事务对象 t 作为选项传入，表示该操作属于当前事务。接着，修改用户余额，并且使用 user.save() 方法保存修改，同样将事务对象 t 作为选项传入。如果在事务中的任意一个操作失败，事务会自动回滚，并且抛出一个异常。如果所有操作都成功，则会自动提交事务。需要注意的是，在开启事务时需要使用 await 或者 then() 等待事务结束后再处理事务的结果。 总结本文介绍了如何使用 Sequelize 在 Node.js 环境下操作数据库，包括创建数据表、增删改查等基本操作。可以根据具体需求进行使用。
使用微信交易组件打造自己的小商城
说明本项目采用Taro-React进行开发。UI参考来自老孙的店。先上效果图初始化小程序购物组件app.config.ts plugins:{ 'mini-shop-plugin':{ version:'latest', provider:'wx34345ae5855f892d' } },app.tsxconst miniShopPlugin = Taro.requirePlugin('mini-shop-plugin'); //下面的代码在componentDidMount执行 //需要异步执行 否则拿不到小程序当前小程序实例 setTimeout(()=>{ miniShopPlugin.initApp(Taro.getApp(), Taro) },1)至此已经初始化成功到这里就可以自己开发啦请参考文档 小程序商业化能力
Vue3快速上手1.Vue3简介2020年9月18日，Vue.js发布3.0版本，代号：One Piece（海贼王）耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者github上的tags地址：https://github.com/vuejs/vue-next/releases/tag/v3.0.02.Vue3带来了什么1.性能的提升打包大小减少41%初次渲染快55%, 更新渲染快133%内存减少54%......2.源码的升级使用Proxy代替defineProperty实现响应式重写虚拟DOM的实现和Tree-Shaking......3.拥抱TypeScriptVue3可以更好的支持TypeScript4.新的特性Composition API（组合API）setup配置ref与reactivewatch与watchEffectprovide与inject......新的内置组件FragmentTeleportSuspense其他改变新的生命周期钩子data 选项应始终被声明为一个函数移除keyCode支持作为 v-on 的修饰符......一、创建Vue3.0工程1.使用 vue-cli 创建官方文档：https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create## 查看@vue/cli版本，确保@vue/cli版本在4.5.0以上 vue --version ## 安装或者升级你的@vue/cli npm install -g @vue/cli ## 创建 vue create vue_test ## 启动 cd vue_test npm run serve2.使用 vite 创建官方文档：https://v3.cn.vuejs.org/guide/installation.html#vitevite官网：https://vitejs.cn什么是vite？—— 新一代前端构建工具。优势如下：开发环境中，无需打包操作，可快速的冷启动。轻量快速的热重载（HMR）。真正的按需编译，不再等待整个应用编译完成。传统构建 与 vite构建对比图## 创建工程 npm init vite-app <project-name> ## 进入工程目录 cd <project-name> ## 安装依赖 npm install ## 运行 npm run dev二、常用 Composition API官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html1.拉开序幕的setup理解：Vue3.0中一个新的配置项，值为一个函数。setup是所有Composition API（组合API）“ 表演的舞台 ”。组件中所用到的：数据、方法等等，均要配置在setup中。setup函数的两种返回值：若返回一个对象，则对象中的属性、方法, 在模板中均可以直接使用。（重点关注！）若返回一个渲染函数：则可以自定义渲染内容。（了解）注意点：尽量不要与Vue2.x配置混用Vue2.x配置（data、methos、computed...）中可以访问到setup中的属性、方法。但在setup中不能访问到Vue2.x配置（data、methos、computed...）。如果有重名, setup优先。setup不能是一个async函数，因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。（后期也可以返回一个Promise实例，但需要Suspense和异步组件的配合）2.ref函数作用: 定义一个响应式的数据语法: const xxx = ref(initValue)创建一个包含响应式数据的引用对象（reference对象，简称ref对象）。JS中操作数据： xxx.value模板中读取数据: 不需要.value，直接： {{xxx}} 备注：接收的数据可以是：基本类型、也可以是对象类型。基本类型的数据：响应式依然是靠Object.defineProperty()的get与set完成的。对象类型的数据：内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。3.reactive函数作用: 定义一个对象类型的响应式数据（基本类型不要用它，要用ref函数）语法：const 代理对象= reactive(源对象)接收一个对象（或数组），返回一个代理对象（Proxy的实例对象，简称proxy对象）reactive定义的响应式数据是“深层次的”。内部基于 ES6 的 Proxy 实现，通过代理对象操作源对象内部数据进行操作。4.Vue3.0中的响应式原理vue2.x的响应式实现原理：对象类型：通过Object.defineProperty()对属性的读取、修改进行拦截（数据劫持）。数组类型：通过重写更新数组的一系列方法来实现拦截。（对数组的变更方法进行了包裹）。Object.defineProperty(data, 'count', { get () {}, set () {} })存在问题：新增属性、删除属性, 界面不会更新。直接通过下标修改数组, 界面不会自动更新。Vue3.0的响应式实现原理:通过Proxy（代理）: 拦截对象中任意属性的变化, 包括：属性值的读写、属性的添加、属性的删除等。通过Reflect（反射）: 对源对象的属性进行操作。MDN文档中描述的Proxy与Reflect：Proxy：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ProxyReflect：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflectnew Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom' 5.reactive对比ref从定义数据角度对比：ref用来定义：基本类型数据。reactive用来定义：对象（或数组）类型数据。备注：ref也可以用来定义对象（或数组）类型数据, 它内部会自动通过reactive转为代理对象。从原理角度对比：ref通过Object.defineProperty()的get与set来实现响应式（数据劫持）。reactive通过使用Proxy来实现响应式（数据劫持）, 并通过Reflect操作源对象内部的数据。从使用角度对比：ref定义的数据：操作数据需要.value，读取数据时模板中直接读取不需要.value。reactive定义的数据：操作数据与读取数据：均不需要.value。6.setup的两个注意点setup执行的时机在beforeCreate之前执行一次，this是undefined。setup的参数props：值为对象，包含：组件外部传递过来，且组件内部声明接收了的属性。context：上下文对象attrs: 值为对象，包含：组件外部传递过来，但没有在props配置中声明的属性, 相当于 this.$attrs。slots: 收到的插槽内容, 相当于 this.$slots。emit: 分发自定义事件的函数, 相当于 this.$emit。7.计算属性与监视1.computed函数与Vue2.x中computed配置功能一致写法import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }2.watch函数与Vue2.x中watch配置功能一致两个小“坑”：监视reactive定义的响应式数据时：oldValue无法正确获取、强制开启了深度监视（deep配置失效）。监视reactive定义的响应式数据中某个属性时：deep配置有效。//情况一：监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二：监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三：监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据，则无法正确获得oldValue！！ 若watch监视的是reactive定义的响应式数据，则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四：监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五：监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性，所以deep配置有效3.watchEffect函数watch的套路是：既要指明监视的属性，也要指明监视的回调。watchEffect的套路是：不用指明监视哪个属性，监视的回调中用到哪个属性，那就监视哪个属性。watchEffect有点像computed：但computed注重的计算出来的值（回调函数的返回值），所以必须要写返回值。而watchEffect更注重的是过程（回调函数的函数体），所以不用写返回值。//watchEffect所指定的回调中用到的数据只要发生变化，则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })8.生命周期vue2.x的生命周期vue3.0的生命周期1Vue3.0中可以继续使用Vue2.x中的生命周期钩子，但有有两个被更名：beforeDestroy改名为 beforeUnmountdestroyed改名为 unmountedVue3.0也提供了 Composition API 形式的生命周期钩子，与Vue2.x中钩子对应关系如下：beforeCreate===>setup()created=======>setup()beforeMount ===>onBeforeMountmounted=======>onMountedbeforeUpdate===>onBeforeUpdateupdated =======>onUpdatedbeforeUnmount ==>onBeforeUnmountunmounted =====>onUnmounted9.自定义hook函数什么是hook？—— 本质是一个函数，把setup函数中使用的Composition API进行了封装。类似于vue2.x中的mixin。自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。10.toRef作用：创建一个 ref 对象，其value值指向另一个对象中的某个属性。语法：const name = toRef(person,'name')应用: 要将响应式对象中的某个属性单独提供给外部使用时。扩展：toRefs 与toRef功能一致，但可以批量创建多个 ref 对象，语法：toRefs(person)三、其它 Composition API1.shallowReactive 与 shallowRefshallowReactive：只处理对象最外层属性的响应式（浅响应式）。shallowRef：只处理基本数据类型的响应式, 不进行对象的响应式处理。什么时候使用?如果有一个对象数据，结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。如果有一个对象数据，后续功能不会修改该对象中的属性，而是生新的对象来替换 ===> shallowRef。2.readonly 与 shallowReadonlyreadonly: 让一个响应式数据变为只读的（深只读）。shallowReadonly：让一个响应式数据变为只读的（浅只读）。应用场景: 不希望数据被修改时。3.toRaw 与 markRawtoRaw：作用：将一个由reactive生成的响应式对象转为普通对象。使用场景：用于读取响应式对象对应的普通对象，对这个普通对象的所有操作，不会引起页面更新。markRaw：作用：标记一个对象，使其永远不会再成为响应式对象。应用场景:有些值不应被设置为响应式的，例如复杂的第三方类库等。当渲染具有不可变数据源的大列表时，跳过响应式转换可以提高性能。4.customRef作用：创建一个自定义的 ref，并对其依赖项跟踪和更新触发进行显式控制。实现防抖效果： {{keyword}} 5.provide 与 inject作用：实现祖与后代组件间通信套路：父组件有一个 provide 选项来提供数据，后代组件有一个 inject 选项来开始使用这些数据具体写法：祖组件中：setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }后代组件中：setup(props,context){ ...... const car = inject('car') return {car} ...... }6.响应式数据的判断isRef: 检查一个值是否为一个 ref 对象isReactive: 检查一个对象是否是由 reactive 创建的响应式代理isReadonly: 检查一个对象是否是由 readonly 创建的只读代理isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理四、Composition API 的优势1.Options API 存在的问题使用传统OptionsAPI中，新增或者修改一个需求，就需要分别在data，methods，computed里修改 。 2.Composition API 的优势我们可以更加优雅的组织我们的代码，函数。让相关功能的代码更加有序的组织在一起。 五、新的组件1.Fragment在Vue2中: 组件必须有一个根标签在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中好处: 减少标签层级, 减小内存占用2.Teleport什么是Teleport？—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。<teleport to="移动位置"> 我是一个弹窗 关闭弹窗 </teleport>3.Suspense等待异步组件时渲染一些额外内容，让应用有更好的用户体验使用步骤：异步引入组件import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))使用Suspense包裹组件，并配置好default 与 fallback 我是App组件 <Suspense> <Child/> 加载中..... </Suspense> 六、其他1.全局API的转移Vue 2.x 有许多全局 API 和配置。例如：注册全局组件、注册全局指令等。//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: ' Clicked {{ count }} times. ' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }Vue3.0中对这些API做出了调整：将全局的API，即：Vue.xxx调整到应用实例（app）上2.x 全局 API（Vue）3.x 实例 API (app)Vue.config.xxxxapp.config.xxxxVue.config.productionTip移除Vue.componentapp.componentVue.directiveapp.directiveVue.mixinapp.mixinVue.useapp.useVue.prototypeapp.config.globalProperties2.其他改变data选项应始终被声明为一个函数。过度类名的更改：Vue2.x写法.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }Vue3.x写法.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }移除keyCode作为 v-on 的修饰符，同时也不再支持config.keyCodes移除v-on.native修饰符父组件中绑定事件<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />子组件中声明自定义事件 !!! 移除过滤器（filter） !!!过滤器虽然这看起来很方便，但它需要一个自定义语法，打破大括号内表达式是 “只是 JavaScript” 的假设，这不仅有学习成本，而且有实现成本！建议用方法调用或计算属性去替换过滤器。......
直接贴代码，省市级数据来自自定义的仓库index.tsximport { View, PickerView, PickerViewColumn, Text } from "@tarojs/components"; import "./index.scss"; import React, { FC, useEffect, useState } from "react"; import Row from "@/component/Row"; import { useStores } from "@/store"; import { observer } from "mobx-react-lite"; import classNames from "classnames"; interface AddressPickerProps { //父组件接值回调 onSure?: (address: [string, string][]) => void; //组件初始值 defaultValue?: [string, string][]; } const AddressPicker: FC<AddressPickerProps> = (props) => { //接受一个回调的通知函数 和组件初始值 const { onSure, defaultValue } = props; //连接仓库 const { userStore } = useStores(); const address = userStore.address; //选定之后显示给用户的文本 const [adressText, setAdressText] = useState(""); //是否打开选择面板 const [pickerShow, setPickerShow] = useState(false); //关闭面板函数 const onClosd = () => setPickerShow(false); //地市级下标 const [state, setState] = useState([0, 0, 0]); //监听三列滑动改变 const cityChange = (e) => { if (e.detail.value[0] !== state[0]) { //省份发生改变 return setState([e.detail.value[0], 0, 0]); } if (e.detail.value[1] !== state[1]) { //市级发生改变 return setState([state[0], e.detail.value[1], 0]); } setState(e.detail.value); }; //省市级数组 格式 [['100001',''北京],[‘100002’,'天津']] const currentProvinces = Object.entries(address[86]); //当前地级市数组 const currentCitys = Object.entries(address[currentProvinces[state[0]][0]]); //当前县数组 const currentAreas = Object.entries(address[currentCitys[state[1]][0]]); //返回最终结果 const handleSure = () => { const tempAreaInfo = [ currentProvinces[state[0]], currentCitys[state[1]], currentAreas[state[2]], ]; //关闭弹框 onClosd(); //设置显示文字 setAdressText(tempAreaInfo.map((item) => item[1]).join("")); //通知调用的组件 吧最终结果返回 onSure && onSure(tempAreaInfo); }; //classNames 动态声明 const labelClasses = classNames("address-picker-primary", { "address-picker-active": !!adressText, }); const pickerClasses = classNames("address-picker-container", { "show address-picker-container-active": pickerShow, }); //完成初始化赋值 useEffect(() => { if (defaultValue) { //如果传入初始值 const provinceCode = defaultValue[0][0]; //省级key const cityCode = defaultValue[1][0]; //市级key const areaCode = defaultValue[2][0]; //省级key //省级下标 const currentProvincesIndex = currentProvinces.findIndex( (v) => v[0] === provinceCode ); //市级下标 const currentCityIndex = Object.entries(address[provinceCode]).findIndex( (v) => v[0] === cityCode ); //县级下标 const currentAreaIndex = Object.entries(address[cityCode]).findIndex( (v) => v[0] === areaCode ); //对齐下标 setState([currentProvincesIndex, currentCityIndex, currentAreaIndex]); //对齐展示文字 setAdressText(defaultValue.map((item) => item[1]).join("")); } }, []); return ( <View> <Row onClick={() => setPickerShow(true)} showRightArrow title="所在地区"> <Text className={labelClasses}>{adressText || "请选择地区"}</Text> </Row> <View onClick={() => onClosd()} className={pickerClasses}> <View onClick={(event) => event.stopPropagation()} className="picker-content" > <View className="dialog-header"> <View onClick={() => onClosd()} className="dialog-button cancel"> 取消 </View> <View className="dialog-title">请选择地区</View> <View className="dialog-button" onClick={handleSure}> 确定 </View> </View> <PickerView onChange={cityChange} value={state} className="picker-view-wrap" > <PickerViewColumn> {currentProvinces.map((province, index) => { return ( <View className="picker-item" key={index}> {province[1]} </View> ); })} </PickerViewColumn> <PickerViewColumn> {currentCitys.map((city, index) => { return ( <View className="picker-item" key={index}> {city[1]} </View> ); })} </PickerViewColumn> <PickerViewColumn> {currentAreas.map((area, index) => { return ( <View className="picker-item" key={index}> {area[1]} </View> ); })} </PickerViewColumn> </PickerView> </View> </View> </View> ); }; export default observer(AddressPicker);index.scss@keyframes back { 0% { background: rgba(255, 255, 255, 0); opacity: 0; } 100% { background: rgba(0, 0, 0, 0.7); opacity: 1; } } @keyframes up { 0% { transform: translateY(600px);opacity: 0; } 100% { transform: translateY(0);opacity: 1; } } .address-picker-container::after{ content: ''; position: absolute; left: 0; top: 0; bottom: 0; right: 0; background: rgba(0, 0, 0, 1); opacity: 0; transition: opacity .3s ease-in-out; } .address-picker-container-active::after{ opacity: 0.7; // animation-fill-mode: forwards; } .address-picker-container { width: 100%; height: 100vh; display: flex; z-index: 12; // background: rgba(0, 0, 0, 0.7); flex-direction: column; justify-content: center; align-items: center; position: fixed; bottom: 0px; left: 0px; visibility: hidden; &.show { visibility: visible; .picker-content { transform: translateY(0); opacity: 1; // transform: translateY(200%); // transition: all .4s ease-in-out; //animation: up .3s ease-in-out forwards; } } } .picker-content { border-top-left-radius: 32px; border-top-right-radius: 32px; position: absolute; height: 660px; z-index: 999; width: 100%; bottom: 0; background-color: #fff; transition: all .3s ease-in-out; transform: translateY(600px); opacity: 0; .picker-view-wrap { width: 100%; height: 500px; margin-top: -50px; } } .picker-item { line-height: 70px; font-size: 30px; text-align: center; text-overflow: ellipsis; white-space: nowrap; } .dialog-header { width: 100%; // background: #ededed; display: flex; justify-content: space-between; align-items: center; border-radius: 30px; .dialog-title { color: #2E2E2E; font-size: 34px; } .dialog-button { display: inline-block; text-align: center; font-size: 34px; color: #846B57; padding: 50px; &.cancel { color: #999999; } } } .address-picker-primary{ padding-right: 22px; color: #999999; } .address-picker-active{ color: #262626; }开始使用import AddressPicker from "@/component/SelectCity"; <AddressPicker defaultValue={[ ["340000", "安徽省"], ["340700", "铜陵市"], ["340705", "铜官区"], ]} onSure={(e) => console.log(e)} /> //defaultValue可不传 用到的省市级数据源
适用场景说明当我们在React/Vue等使用webpack构建项目中需要大量使用important 来引入我们所需要的模块时。就像下面这样。例如React/Vue 的路由或者store部分。import A from 'components/A' import B from 'components/B' import C from 'components/C' import D from 'components/D' // ...解决方法require.context(directory, useSubdirectories, regExp)directory: 要查找的文件路径（String）useSubdirectories: 是否查找子目录（为布尔值）regExp: 要匹配文件的正则就像下面一样使用//寻找当前目录下所有ts文件（包含子目录） const modulesFiles = require.context("./", true, /\.ts$/); //一个webpack的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块值得注意的是require.context函数执行后返回的是一个函数,并且这个函数有3个属性keys: 返回匹配成功模块的名字组成的数组resolve: 接受一个参数request，request返回这个匹配文件相对于整个工程的相对路径。id: 执行环境的id，返回的是一个字符串，主要用在module.hot.accept，应该是热加载。当然上面的我也没看懂 ，直接整活就Vans了在我的项目中使用到了mobx作为全局状态管理 ，把store要挂载到全局时，我是这么写的。/* * @Author: XiaoHuwei * @Date: 2021-10-27 20:04:33 * @LastEditors: XiaoHuwei * @LastEditTime: 2021-10-27 22:27:50 * @FilePath: /txcb_mini/src/store/index.ts */ import React from "react"; //遍历同级目录包含ts子文件 自动加载./store下面所有store 实现全量注册 const modulesFiles = require.context("./", true, /\.ts$/); //组成store的集合 const allStore = modulesFiles.keys().reduce((allStore, modulePath) => { //根据模块路径取到该模块 const value = modulesFiles(modulePath); //不包含本文件 if (modulePath !== "./index.ts") { //获取到仓库名 获取该模块下面所有初始值 allStore[value.default.storeName] = value.default; } return allStore; }, {}); const StoresContext = React.createContext({ ...allStore, }); export const useStores = () => React.useContext(StoresContext);我的一个store之一代码如下/* * @Author: XiaoHuwei * @Date: 2021-10-11 11:47:36 * @LastEditors: XiaoHuwei * @LastEditTime: 2021-10-27 21:46:59 * @FilePath: /txcb_mini/src/store/home/counter.ts */ import { observable } from "mobx"; import { getLogin } from "@/service/test"; const counterStore = observable({ //////仓库名 重要部分！！storeName 该变量每次声明仓库必填！ storeName: "counterStore", counter: 0, loading: "false", Login(data) { this.loading = "true"; getLogin(data) .then((res) => { console.log(res.data); this.loading = "true"; }) .catch((error) => console.log(error)); }, increment() { this.counter++; }, decrement() { this.counter--; }, incrementAsync() { setTimeout(() => { this.counter++; }, 1000); }, }); export default counterStore;市面上有一套架构体系(umi+dva)，相信同学们用过，他的store写在modules文件下即可实现全局调用，效果跟这一样，实际上是umi在底部为我们单独做了处理。
说一下最近公司项目的需求实现方式视频预加载与本地化如果页面有若干个视频，我们想做到页面刚进来的时候就把所有的视频缓存在本地，下次进页面就可以直接读取缓存而不是向服务器重复请求。实现方法const saveVideo = src => { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open('GET', src, true); xhr.responseType = 'blob'; xhr.onload = function () { if (this.status === 200) { const videoBlob = this.response; const blobSrc = URL.createObjectURL(videoBlob); // IE10+ resolve(blobSrc) } }; xhr.send(); }) };使用saveVideo(`xxxx.mp4`).then(res=>console.log(res))监听localStorage变化需要重写 window.localStorage.setItem() var orignalSetItem = localStorage.setItem; window.localStorage.setItem = function(key,newValue){ var setItemEvent = new Event("setItemEvent"); setItemEvent.key = key; setItemEvent.newValue = newValue; window.dispatchEvent(setItemEvent); orignalSetItem.apply(this,arguments); };使用window.addEventListener("setItemEvent", function(){console.log(e)}) window.localStorage.setItem(name, value)//console.log(e)给你的React 加上动画npm i animate.css -S使用import 'animate.css' 云屏编号：100006 这样就可以了。更多效果请参阅 animate.css图片未加载完成添加loading画面{ test.boxList.data.map(item => { return ( GotoDetail(item)} key={item.image} className={`${styles.imgbox} animate__animated animate__fadeInUp`}> //关键部分 ) }) }方法//判断浏览器 var Browser = new Object(); Browser.userAgent = window.navigator.userAgent.toLowerCase(); Browser.ie = /msie/.test(Browser.userAgent); Browser.Moz = /gecko/.test(Browser.userAgent); window.Imagess = (url, callback, error) => { var val = url; var img = new Image(); if (Browser.ie) { img.onreadystatechange = function () { if (img.readyState == "complete" || img.readyState == "loaded") { callback(img); } } } else { img.onload = function () { if (img.complete == true) { callback(img); } } } //如果因为网络或图片的原因发生异常，则显示该图片 if (error) { img.onerror = error; } else { img.onerror = function () { img.src = imgloading } } img.src = val; } //调用方法 let imglist = document.querySelectorAll('.imgLoading') for (let i = 0; i < imglist.length; i++) { let tt = imglist[i]; //防止重复加载 if (tt.loading == true) { continue; } //没有该属性代表不加载 if (!tt.getAttribute("src-data")) { continue; } tt.loading = true; tt.src = ’loading‘;//这里写上未加载出来之前显示的内容 window.Imagess(tt.getAttribute("src-data"), function (obj) { tt.src = obj.src; tt.removeAttribute("src-data"); }); } Antd 表格导出为Excel/* * @Author: XiaoHuwei * @Date: 2020-07-10 10:16:16 * @LastEditors: XiaoHuwei * @LastEditTime: 2020-07-10 10:33:57 * @FilePath: /screen-cloud-bg/src/utils/Export.js * @Description: Global export for Exel function */ import { File } from 'better-xlsx'; import { saveAs } from 'file-saver'; const ExportExcel = (column, dataSource, fileName = 'example')=> { // 新建工作谱 const file = new File(); // 新建表 let sheet = file.addSheet('sheet-test'); // 获取表头行数 let depth = getDepth(column); // 获取表头的列数 let columnNum = getColumns(column); // 新建表头行数 let rowArr = []; for (let k = 0; k < depth; k++) { rowArr.push(sheet.addRow()); } // 根据列数填充单元格 rowArr.map(ele => { for (let j = 0; j < columnNum; j++) { let cell = ele.addCell(); cell.value = j; } }); // 初始化表头 init(column, 0, 0); // 按顺序展平column let columnLineArr = []; columnLine(column); // 根据column,将dataSource里面的数据排序，并且转化为二维数组 let dataSourceArr = []; dataSource.map(ele => { let dataTemp = []; columnLineArr.map(item => { dataTemp.push({ [item.dataIndex]: ele[item.dataIndex], value: ele[item.dataIndex], }); }); dataSourceArr.push(dataTemp); }); // debugger; // 绘画表格数据 dataSourceArr.forEach((item, index) => { //根据数据,创建对应个数的行 let row = sheet.addRow(); row.setHeightCM(0.8); //创建对应个数的单元格 item.map(ele => { let cell = row.addCell(); if (ele.hasOwnProperty('num')) { cell.value = index + 1; } else { cell.value = ele.value; } cell.style.align.v = 'center'; cell.style.align.h = 'center'; }); }); //设置每列的宽度 for (var i = 0; i < 4; i++) { sheet.col(i).width = 20; } file.saveAs('blob').then(function(content) { saveAs(content, fileName + '.xlsx'); }); // 按顺序展平column function columnLine(column) { column.map(ele => { if (ele.children === undefined || ele.children.length === 0) { columnLineArr.push(ele); } else { columnLine(ele.children); } }); } // 初始化表头 function init(column, rowIndex, columnIndex) { column.map((item, index) => { let hCell = sheet.cell(rowIndex, columnIndex); // 如果没有子元素, 撑满列 if (item.title === '操作') { hCell.value = ''; return; } else if (item.children === undefined || item.children.length === 0) { // 第一行加一个单元格 hCell.value = item.title; hCell.vMerge = depth - rowIndex - 1; hCell.style.align.h = 'center'; hCell.style.align.v = 'center'; columnIndex++; // rowIndex++ } else { let childrenNum = 0; function getColumns(arr) { arr.map(ele => { if (ele.children) { getColumns(ele.children); } else { childrenNum++; } }); } getColumns(item.children); hCell.hMerge = childrenNum - 1; hCell.value = item.title; hCell.style.align.h = 'center'; hCell.style.align.v = 'center'; let rowCopy = rowIndex; rowCopy++; init(item.children, rowCopy, columnIndex); // 下次单元格起点 columnIndex = columnIndex + childrenNum; } }); } // 获取表头rows function getDepth(arr) { const eleDepths = []; arr.forEach(ele => { let depth = 0; if (Array.isArray(ele.children)) { depth = getDepth(ele.children); } eleDepths.push(depth); }); return 1 + max(eleDepths); } function max(arr) { return arr.reduce((accu, curr) => { if (curr > accu) return curr; return accu; }); } // 计算表头列数 function getColumns(arr) { let columnNum = 0; arr.map(ele => { if (ele.children) { getColumns(ele.children); } else { columnNum++; } }); return columnNum; } } export default ExportExcel;
公司最近一个需求 需要通过变量实现复制效果请双击我 哈哈哈哈代码window.copyText = item => {//挂载到全局 var textArea = document.createElement('textArea'); textArea.innerHTML = item; textArea.value = item; document.body.appendChild(textArea); textArea.select(); try{ if(document.execCommand("Copy")){ alert(`${item} 复制成功！`) }else{ alert('复制失败！请手动复制！') } } catch(err){ alert('复制错误！请手动复制！') } document.body.removeChild(textArea); }使用 window.copyText('你好')
介绍柯里化其实是函数式编程的一个过程，在这个过程中我们能把一个带有多个参数的函数转换成一系列的嵌套函数。它返回一个新函数，这个新函数期望传入下一个参数。简单的说就是把一个你写好的具有多个参数的函数可以一级级（递归+闭包）调用（每次传入一个参数）。应用场景我们希望用一个通用的正则模板实现项目中各种类型检查，包括但不限于手机号，邮件地址... { //定义主要柯里化主要逻辑处理 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); } //定义正则验证函数 regExp =>规则 value=>待检验对象 const MyReg = (regExp, value)=>console.log(regExp.test(value)); MyReg(/^1\d{10}$/, '18642838455') // 校验电话号码 true const _check = curry(MyReg) //进行柯里化 const checkPhone = _check(/^1\d{10}$/) //生成工具函数，验证电话号码 checkPhone('18642838455') // 这样就实现了函数柯里化 true }关键部分是 const curry = fn => { const _c = (restNum, argsList) => restNum === 0 ? fn(...argsList) : x => _c(restNum - 1, [...argsList, x]); return _c(fn.length, []); }理解首先curry函数接收一个函数，这个函数是我们真正的处理逻辑的函数然后通过闭包，声明一个函数_c，_c接收两个参数，一个是真正函数的参数列表长度的计数器，一个是传入参数的列表在_c函数中判断真正函数的参数列表是否等于0，等于0代表参数已经全部都传入进来了，可以调用真正的函数去进行计算，并返回fn函数进行计算然后返回结果；如果参数列表不等于0，则把参数列表的计数器-1，并把传入参数进行合并，然后使用返回_c函数供尾递归调用。上面就是柯里化的大体思路，主要实现是依赖闭包和尾递归来实现的，所以在性能上有优化的同时也是有一定的损失，对于性能要求很苛刻的程序可能需要酌情考虑，一般情况下合理使用柯里化是可以把代码的可读性提高很多。
最近在研究小程序这方面的东西，使用到了京东基于 React 的小程序开发框架 Taro ，相比 mpvue 和 uni-app 这两个框架来说，Taro 显得更优雅，更稳重，大厂在迭代，就是不一样，一句话 ，就是坑少一点～前置知识看本篇教程前需要你掌握 dva ,React Hooks ,Redux ,还有小程序的开发流程。希望你知其然，并知其所以然。如果你不具备以上任意一点，建议先停下来，下面我给出地址，先自行学习之后再过来看我这篇文章 。Redux-React全局状态管理工具之一dva-让你的全局状态管理更简单React-用于构建用户界面的 JavaScript 库React Hooks-更优雅的React应用书写方式小程序Taro-基于React的小程序框架正式开始安装 Taro脚手架# 使用 npm 安装 CLI $ npm install -g @tarojs/cli # OR 使用 yarn 安装 CLI $ yarn global add @tarojs/cli # OR 安装了 cnpm，使用 cnpm 安装 CLI $ cnpm install -g @tarojs/cli因为我电脑安装了 cnpm 镜像源，所以下文我都会用 cnpm脚手架完成后使用脚手架初始化一个新项目在你需要建立项目的文件夹打开 CMDtaro init test//我们演示的项目名叫test然后跟着流程走，这边为了减少你的学习成本，建议不要选择 Typescript ,css选择less,模版选择默认模版。 最后成功之后，用你的编辑器打开test目录。你的目录结构跟我的应该是一样的 先删除 ./src/pages 下面的 index 文件夹。然后命令行在test 目录打开，安装我们需要用的扩展cnpm install --save @tarojs/async-await //没想到吧 在Taro里面使用 async和 await 需要安装这个扩展 cnpm install --save redux @tarojs/redux @tarojs/redux-h5 redux-thunk redux-logger //Redux全家桶 cnpm install --save dva-core dva-loading //dva cnpm install --save taro-axios //在Taro里面使用axios 需要安装他这四个命令安装完之后，打开./src 目录 ，新建两个文件夹 ，一个叫 utils 一个叫 models 注意这两个文件夹应该跟你的 pages 文件夹同级。在 utils 文件夹下面新建文件 dva.js 写上下面的内容 注册dvaimport { create } from 'dva-core'; import { createLogger } from 'redux-logger'; import createLoading from 'dva-loading'; let app; let store; let dispatch; function createApp(opt) { // redux日志 // opt.onAction = [createLogger()]; app = create(opt); app.use(createLoading({})); if (!global.registered) opt.models.forEach(model => app.model(model)); global.registered = true; app.start(); store = app._store; app.getStore = () => store; dispatch = store.dispatch; app.dispatch = dispatch; return app; } export default { createApp, getDispatch() { return app.dispatch; } }在 utils 文件夹下面新建文件 request.js 写上下面的内容 封装一个简单的axios 实际你的项目中可能不一样import axios from "taro-axios"; const baseURL = `https://blog.xiaohuwei.cn` const service = axios.create({ baseURL: baseURL, withCredentials: true, timeout: 300000 }); service.interceptors.response.use( response => { return response.data; }, error => { return Promise.reject(error) }) export default service在 model 文件夹下面新建文件 model.js 写上下面的内容 连接你每个页面的仓库注册到全局import index from '../pages/index/model'; export default [index];你会发现我们刚才吧 index 文件夹已经删除了 ，这里先这样写，后面我在做说明。下一步 修改 ./src/app.jsx 作用就是把我们所有的仓库连接进来 方便你学习 下面给出修改后的全部代码import '@tarojs/async-await' import Taro, { Component } from '@tarojs/taro' import Index from './pages/index' import dva from './utils/dva'; import models from './models/models'; import { Provider } from '@tarojs/redux'; import './app.less' const dvaApp = dva.createApp({ initialState: {}, models: models, }); const store = dvaApp.getStore(); class App extends Component { config = { pages: [ 'pages/index/index' ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'WeChat', navigationBarTextStyle: 'black' } } componentDidMount() { } componentDidShow() { } componentDidHide() { } componentDidCatchError() { } // 在 App 类中的 render() 函数没有实际作用 // 请勿修改此函数 render() { return ( <Provider store={store}> <Index /> </Provider> ) } } Taro.render(<App />, document.getElementById('app'))最后一步在你项目 根目录 ，为了跟我保持一致，希望你在根目录新建一个叫 page.js的文件 这个文件我们用来自动生成新页面，包含新页面的 ui层 service层 和 model层方便快速开发。内容如下/** * pages模版快速生成脚本,执行命令 npm run temp `文件名` */ const fs = require('fs'); const dirName = process.argv[2]; if (!dirName) { console.log('文件夹名称不能为空！'); console.log('示例：npm run temp test'); process.exit(0); } // 页面模版 const indexTep = `import Taro,{useEffect} from '@tarojs/taro'; import { View,Text } from '@tarojs/components'; import { connect } from '@tarojs/redux'; import './index.less'; const ${titleCase(dirName)} = props =>{ const {${dirName},loading} = props; useEffect(() => { console.log(props) }, []) return ( <View className="${dirName}-page"> <Text>正如你所见这是你的${dirName}页面</Text> </View> ) } ${titleCase(dirName)}.config = { navigationBarTitleText: '${dirName}' } //全局样式继承 你可以关掉 ${titleCase(dirName)}.options = { addGlobalClass: true } export default connect( ({ ${dirName}, loading })=>({ ${dirName}, loading }))(${titleCase(dirName)}) `; // less文件模版 const lessTep = ` .${dirName}-page { } `; // model文件模版 const modelTep = `import * as ${dirName}Api from './service'; export default { namespace: '${dirName}', state: { keai:'测试数据666' }, effects: { * effectsDemo(_, { call, put }) { const { status, data } = yield call(${dirName}Api.demo, {}); if (status === 'ok') { yield put({ type: 'save', payload: { topData: data, } }); } }, }, reducers: { save(state, { payload }) { return { ...state, ...payload }; }, }, }; `; // service页面模版 const serviceTep = `import Request from '../../utils/request'; export const demo = (data) => { return Request({ url: '路径', method: 'POST', data, }); }; `; fs.mkdirSync(`./src/pages/${dirName}`); // mkdir $1 process.chdir(`./src/pages/${dirName}`); // cd $1 fs.writeFileSync('index.js', indexTep); fs.writeFileSync('index.less', lessTep); fs.writeFileSync('model.js', modelTep); fs.writeFileSync('service.js', serviceTep); console.log(`模版${dirName}已创建,请手动按照格式增加到./src/models`); function titleCase(str) { const array = str.toLowerCase().split(' '); for (let i = 0; i < array.length; i++) { array[i] = array[i][0].toUpperCase() + array[i].substring(1, array[i].length); } const string = array.join(' '); return string; } process.exit(0);完成之后 去 package.json 里面的 scripts 添加一条 命令 "temp": "node page.js"如果你能够走到这一步 ，说明你已经成功了 ，跟我的目录结构 对照一下 命令行在你的项目目录打开，我们来使用 脚本生成第一个页面 indexnpm run temp index你会发现你的./src/pages 目录下多了 index 文件夹 并且文件夹下面已经为你初始化好了所有你需要的文件，但是并没有结束。就是上文提到的 ，每次新建一个页面都需要去 ./src/models/models.js 手动 按照我给你的格式引入一次，因为我这个组件叫 index，所以我给你的默认就是 index,所以我这里不用改，你做项目的时候，是需要手动加的。试试效果！npm run dev:weapp 好啦，所有的都完成啦，开始开发你的小程序吧！～说下 Taro的注意事项当你 使用到了 图片这些静态资源的时候 ，需要使用 import 的语法引用。代码仓库