vue脚手架多页自动化生成实践
前言
在前端开发过程中,常常面对多种业务场景。到目前为止,前端对于不同场景的处理通常会采用不同的渲染方案来组合处理,常见的渲染方案包括:CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generation)、ISR(Incremental Site Rendering)、DPR(Distributed Persistent Rendering)、NSR(Native Side Rendering)以及ESR(Edge Side Rendering)等。在目前项目开发过程中,遇到了需要构建门户类应用的需求,而团队主要技术栈以Vue为主,整个技术方案以Vue全家桶进行构建。因此,本文旨在针对门户类应用的场景下的Vue脚手架构建方案的一些总结和分析,通过自动化的配置脚本来生成模板化的多页应用实践,以期能够给读者提供一个基于Vue全家桶的门户类工程构建方案。
架构
对于门户类型的应用,由于其大部分内容变动内容较少,而对于部分关键页面却会有动态更新的要求,因而在通常会采用多页形式的处理配合部分单页应用中的优势进行处理。因而,在技术选型方面,团队采用了预渲染配合多页的方式实现门户类SEO及首屏加载快的需求。同时,结合单页应用的优势,在多页中的部分关键页面中采用单页中的优点,如:路由切换快、用户体验好等。综上,架构风格采用ISR的增量渲染方案,由于项目背景的特殊性,无法配合常规CDN等部署方案特点,但可以使用云原生相关的中间件实现类似效果,整体部署仍以“云+端”的形式为主。
目录
selfService├─portal
├─ build // vue cli打包所需的options中内容一些抽离,对其中做了环境区分
| ├─ demo
| | ├─config.json
| | ├─configureWebpack.js
| ├─ dev
| | ├─ config.json
| | ├─ configureWebpack.js
| ├─ production
| | ├─ config.json
| | ├─ configureWebpack.js
| ├─ chainWebpack.js
| ├─ configureWebpack.js
| ├─ devServer.js
| ├─ pages.js
| ├─ routes.js
| ├─ index.js
| ├─ utils.js
├─ deploy // 不同环境的部署
| ├─ demo
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ dev
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ production
| | ├─ default.conf
| | ├─ Dockerfile
| | ├─ env.sh
| ├─ build.sh
├─ public
| ├─ pageA // pageA的html,这里可以存放一些静态资源,非构建状态下的js、css等
| | ├─ index.html
| ├─ pageB // pageB的html,这里可以存放一些静态资源,非构建状态下的js、css等
| | ├─ index.html
| ├─ favicon.ico
├─ src
| ├─ assets // 存放小资源,通常为必须,如:logo等,其他静态资源请放入cdn或者public下
| | ├─ logo.png
| ├─ components // 公共组件,可抽离多个静态页面的公共组件
| | ├─ Header.vue
| ├─ router
| | ├─ pageA // pageA的router,使用了history模式
| | ├─ index.js
| | ├─ pageB // pageB的router,使用了history模式
| | ├─ index.js
| ├─ store
| | ├─ pageA // pageA的Vuex
| | ├─ index.js
| | ├─ pageB // pageB的Vuex
| | ├─ index.js
| ├─ views
| | ├─ pageA // pageA的页面,写法和之前一个的单页应用一致
| | ├─ main.js // 注入了mode,挂载到了vue的原型上,使用this可以获取环境变量
| | ├─ pageA.vue
| | ├─ pageB // pageB的页面,写法和之前一个的单页应用一致
| | ├─ main.js // 注入了mode,挂载到了vue的原型上,使用this可以获取环境变量
| | ├─ pageB.vue
├─ scripts
├─ babel.config.js // 配置es转化语法
├─ vue.config.js // vue cli打包相关配置
├─ app.json // 存放各个多页应用的public、router、vuex、views入口地址
实践
配置
Vue脚手架中配置多页主要是使用Webpack中的pages入口配置,这里主要是修改vue.config.js
中的pages的设置,代码如下:
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
module.exports = {// ...pages: {page3:{entry: "src/views/page3/main.js",template: "public/page3/index.html",filename: "page3.html",title: "page3"}},configureWebpack: config => {config.plugins.push(new PrerenderSPAPlugin({staticDir: path.resolve(__dirname,'../../dist'),routes: ['/page3'],renderer: new Renderer({less: false,//renderAfterDocumentEvent: 'render-event',//renderAfterTime: 5000,//renderAfterElementExists: 'my-app-element'}),}))}
}
其中,如果配置了pages,@vue/cli-service会先清除原有的entry,如果没有index,则devServer默认入口的根路径’/‘仍为index.html;如果有index的key值,则会进行相应的覆盖。在这里,对pages下的key值为对应多页的路径,如:上述代码下的page3,则对应的路径为’/page3.html’;pages下的value可以为字符串,也可以为对象,其中:entry为多页的入口(必选项)、template为模板来源、filename为打包后的输出名称以及title会通过html-webpack-plugin
的插件对template中的<title><%= htmlWebpackPlugin.options.title %></title>
进行替换。
而对于预渲染的应用,这里使用了prerender-spa-plugin和vue-meta-info来进行SEO及首屏加载优化,代码如下:
// ...
import MetaInfo from 'vue-meta-info'Vue.use(MetaInfo)new Vue({router,store,render: h => h(index),mounted () {document.dispatchEvent(new Event('custom-render-trigger'))}
}).$mount('#page3')
脚本
通过上述的配置,基本就可以实现一个 预渲染+多页
的vue脚手架搭建。但是,除了开发环境的配置,对于生产环境、部署等也需要进行一定的设置,这样频繁的操作就会带来一定的功效降低。因而,在前端工程化领域中,通常会进行一定的脚本化或者说脚手架方案的构建。这里,在目前项目中,团队对多页应用的配置进行了自动化的脚本实现。
生成多页的脚本主要通过page.js
进行实现,代码如下:
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
const ora = require('ora');
const { transform, compose } = require('./utils');const spinner = ora();const PAGE_REG = /[a-zA-Z_0-9]/ig;
const rootDir = path.resolve(process.cwd(), '.');// 判断dir目录下是否存在name的文件夹
const isExt = (dir, name) => fs.existsSync(path.join(dir, name));const APP_JSON_EJS = `{"pages": <%= page_name %>
}`;const INDEX_HTML_EJS = `<!DOCTYPE html>
<html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1.0"><link rel="icon" href="../favicon.ico"><title><%= htmlWebpackPlugin.options.title %></title></head><body><noscript><strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><%= page_name %></body>
</html>
`const INDEX_VUE_EJS = `<%= page_name %><script>
export default {
components: {
},
data() {return {};
},
};
</script><style lang="less">
</style>`const MAIN_JS_EJS = `<%= page_name %>`const INDEX_ROUTER_EJS = `import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [<%= page_name %>
]const router = new VueRouter({mode: 'history',routes
})export default router`const INDEX_STORE_EJS = `import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {},mutations: {},actions: {},modules: {}
})
`// inquirer list
const promptList = [{type: 'input',name: 'page_name',message: '请输入你想要创建的多页应用名称',filter: function (v) {return v.match(PAGE_REG).join('')}}
];// nginx的default.conf所需添加内容
const addDefaultConf = page_name => {return `location /${page_name} {root /usr/share/nginx/html;index ${page_name}.html;try_files $uri $uri/ /${page_name}.html;gzip_static on;
}`
};// page_name下的index.html
const addIndexHtml = page_name => {return `<div id="${page_name}" data-server-rendered="true"></div>`
};// page_name下的router
const addRouterIndex = page_name => {return `{path: '/',component: () => import('../../views/${page_name}/index.vue')
},`
};// page_name下的views index.vue
const addViewsIndex = page_name => {return `<template><div>${page_name}</div>
</template>`
};// page_name下的views main.js
const addViewsMain = page_name => {return `import Vue from 'vue'
import index from './index.vue'
import router from '../../router/${page_name}/index.js'
import store from '../../store/${page_name}/index.js'
import MetaInfo from 'vue-meta-info'Vue.use(MetaInfo)import axios from 'axios'Vue.prototype.$mode = process.env.VUE_APP_MODE;Vue.prototype.axios = axios;Vue.config.productionTip = falsenew Vue({router,store,render: h => h(index),mounted () {document.dispatchEvent(new Event('custom-render-trigger'))}
}).$mount('#${page_name}')`
};// page_name下的pages.js
const addPages = page_name => {return JSON.stringify({entry: `src/views/${page_name}/main.js`,template: `public/${page_name}/index.html`,filename: `${page_name}.html`,title: `${page_name}`,})
}const updateApp = page_name => {// 获取pages的数组const pages = require('../app.json')['pages'];if(pages.includes(page_name)) return true;pages.push(page_name);spinner.start()fs.writeFile(`${rootDir}/app.json`, transform(/<%= page_name %>/g, JSON.stringify(pages), APP_JSON_EJS), err => {spinner.color = 'red';spinner.text = 'Loading Update app.json'if(err) {spinner.fail(chalk.red(`更新app.json失败`))return false;} else {spinner.succeed(chalk.green(`更新app.json成功`))return true;}});
}// 处理 public 文件夹下的核心逻辑
const processPublic = args => {const { page_name } = args;if(isExt(`${rootDir}/public`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/public/${page_name}`)}fs.writeFileSync(`${rootDir}/public/${page_name}/index.html`, transform(/<%= page_name %>/g, addIndexHtml(page_name), INDEX_HTML_EJS));// 处理默认页面的跳转const content = require('../app.json')['pages'].map(page => {return `<li><a href="/${page}.html">${page}</a>
</li>`}).join(`
`);const ejs_arr = fs.readFileSync(`${rootDir}/public/index.html`, 'utf-8').split(`<body>`);fs.writeFileSync(`${rootDir}/public/index.html`, ejs_arr[0] + `<body>
`+`<h1>自服务门户</h1>
<ul>${content}
</ul>` + `
</body>
</html>`);return args;
};// 处理 src/views 文件夹下的核心逻辑
const processViews = args => {const { page_name } = args;if(isExt(`${rootDir}/src/views`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/views/${page_name}`)}fs.writeFileSync(`${rootDir}/src/views/${page_name}/index.vue`, transform(/<%= page_name %>/g, addViewsIndex(page_name), INDEX_VUE_EJS));fs.writeFileSync(`${rootDir}/src/views/${page_name}/main.js`, transform(/<%= page_name %>/g, addViewsMain(page_name), MAIN_JS_EJS));return args;
};// 处理 src/router 文件夹下的核心逻辑
const processRouter = args => {const { page_name } = args;if(isExt(`${rootDir}/src/router`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/router/${page_name}`)}fs.writeFileSync(`${rootDir}/src/router/${page_name}/index.js`, transform(/<%= page_name %>/g, addRouterIndex(page_name), INDEX_ROUTER_EJS));return args;
};// 处理 src/store 文件夹下的核心逻辑
const processStore = args => {const { page_name } = args;if(isExt(`${rootDir}/src/store`, page_name)) {return args;} else {fs.mkdirSync(`${rootDir}/src/store/${page_name}`)}fs.writeFileSync(`${rootDir}/src/store/${page_name}/index.js`, INDEX_STORE_EJS);return args;
};// 处理 build 文件夹下的核心逻辑
const processBuild = args => {const { page_name } = args;// 处理 build/page.jsconst pages = require('../build/pages.js');if(Object.keys(pages).includes(page_name)) return args;pages[`${page_name}`] = JSON.parse(addPages(page_name));const PAGES_JS_EJS =`const pages = ${JSON.stringify(pages)}
module.exports = pages;`;fs.writeFileSync(`${rootDir}/build/pages.js`, PAGES_JS_EJS);// 处理 build/routes.jsconst routes = require('../build/routes.js');if(routes.includes(`/${page_name}`)) return args;routes.push(`/${page_name}`);const ROUTES_JS_EJS =`const pages = ${JSON.stringify(routes)}
module.exports = pages;`;fs.writeFileSync(`${rootDir}/build/routes.js`, ROUTES_JS_EJS);return args;
}// 处理 deploy 文件夹下的核心逻辑
const processDeploy = args => {const { page_name } = args;const reg = new RegExp(`location /${page_name}`);['demo', 'dev', 'production'].forEach(item => {const content = fs.readFileSync(`${rootDir}/deploy/${item}/default.conf`, 'utf-8');if(reg.test(content)) return args;const ejs_arr = content.split(`location /api/`)fs.writeFileSync(`${rootDir}/deploy/${item}/default.conf`, transform(/<%= page_name %>/g, addDefaultConf(page_name), ejs_arr[0] + `<%= page_name %>
location /api/`+ ejs_arr[1]));});return args;
};inquirer.prompt(promptList).then(answers => {const page_name = answers.page_name;return updateApp(page_name)}).then(() => {const pages = require('../app.json')['pages'];pages.forEach(page => {console.log('page', page)compose(processDeploy,processBuild, processStore, processRouter, processViews, processPublic)({page_name: page});})}).catch(err => {if(err) {console.log(chalk.red(err))}})
为了更好的实现代码的优雅性,对代码工具进行了抽离,放入到utils.js
中,代码如下:
// 将内容替换进ejs占位符
const transform = ($, content, ejs) => ejs.replace($,content);// 将流程串联
const compose = (...args) => args.reduce((prev,current) => (...values) => prev(current(...values)));module.exports = {transform,compose
}
总结
仅管到目前为止,单页应用仍是前端开发中的主流方案。但是,随着各大应用的复杂度提升,多种方案的建设也都有了来自业界不同的声音,诸如:多种渲染方案、Island架构等都是为了能更好的提升Web领域的体验与开发建设。技术方案的选择不只局限于生态的整合,更重要的是对合适场景的合理应用。
“形而上者谓之道,形而下者谓之器”,各位前端开发者不仅应该只着眼于眼前的业务实现,同时也需要展望未来,站在更高的视野上来俯视技术的走向与演进,共勉!!!
参考
- vue-cli搭建自动化多页面项目(vue高阶)
- Vue-cli配置多页面
- vue预渲染之prerender-spa-plugin插件应用
- 预渲染插件prerender-spa-plugin生成多页面
- CSR、SSR、NSR、ESR傻傻分不清楚,一文帮你理清前端渲染方案!
- vue项目改造SSR(服务端渲染)
- 什么是SSR/SSG/ISR?如何在AWS上托管它们?
- 卷起来,前端建站SSG,SSR,ISR,Hydration, Island…一网打尽
- 你知道吗?SSR、SSG、ISR、DPR 有什么区别?
- SSR、ISR、CSR、SSG有什么区别
- 一文看懂Next.js渲染方法:CSR、SSR、SSG和ISR
相关文章:
vue脚手架多页自动化生成实践
前言 在前端开发过程中,常常面对多种业务场景。到目前为止,前端对于不同场景的处理通常会采用不同的渲染方案来组合处理,常见的渲染方案包括:CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generati…...
【SQL语句优化】
SQL语句优化是提高数据库查询性能的重要手段之一,下面是几种常见的SQL语句优化方法和案例: 减少查询的数据量 减少查询的数据量:使用 WHERE 子句和索引来限制检索行数,只检索需要的行,避免检索全部行数据。 例子&am…...
阿里P8:做测试10年我的一些经验分享,希望你们少走弯路
我是在2015年毕业的,当时是读的普通本科,不上不下的专业水平,毕业的时候,恰好遇到了金融危机。校园招聘里阴差阳错的巧合,让我走上了软件测试工程师的道路。 入职第一天,来了个高大上的讲师,记…...
栈在括号匹配中的应用(栈/链栈 纯C实现)
目录 1 问题背景 2 具体思路 3 代码实现 3.1 顺序栈实现 3.2 链栈实现 1 问题背景 栈的括号匹配问题是指在给定一个字符串(包含多种括号),判断其中的括号是否能够正确匹配,即每个左括号是否有一个对应的右括号与之匹配&#x…...
C语言Switch语句用法
C switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法: switch(expression){case constant-expression :statement(s);break;…...
Curl编码请求参数,API接口请求示例参数
请求参数请求参数:num_iid610947572360 参数说明:num_iid:1688商品ID sales_data:&sales_data1 获取近30天成交数据 agent:&agent1 获取1688分销代发价格数据请求示例 测试入口 Curl PHP PHPsdk JAVA C# Python-- 请求示例 url 默认请求参数已经…...
【C/C++】类型限定符extern、const、Volatile、register
1、extern: 声明一个变量,extern声明的变量没有建立存储空间。 extern int a ; //变量在定义的时候创建存储空间。 ①当我们在编译器中试图运行以下代码,系统会报错。 错误原因是“无法解析外部符号_a”.系统认为变量a是没有开辟内存空间的…...
day54【代码随想录】二刷数组
文章目录前言一、二分查找(力扣724)二、移除元素(力扣27)【双指针】三、有序数组的平方(力扣977)【双指针】四、合并两个有序数组(力扣88)五、长度最小的子数组(力扣209&…...
哪个品牌蓝牙耳机性价比高?性价比高的平价蓝牙耳机推荐
现如今,随着蓝牙技术的进步,蓝牙耳机在人们日常生活中的便捷性更胜从前。越来越多的蓝牙耳机品牌被大众看见、认可。那么,哪个品牌的蓝牙耳机性价比高?接下来,我给大家推荐几款性价比高的平价蓝牙耳机,一起…...
揭秘关于TFRcord的五脏六腑
揭秘关于TFRcord的五脏六腑 前言:本篇文章将演示如何创建、解析和使用tf.Example消息,以及如何在.tfrecord文件之间对tf.Example消息进行序列化、写入和读取。 教程讲解使用的都是结构化数据,文章最后还会演示如果将图片写成.tfrecord文件&am…...
【Shell学习笔记】3.Shell 传递参数及数组
前言 本章介绍Shell的传递参数和数组。 Shell 传递参数 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,…...
【终结Bug】ModuleNotFoundError: No module named ‘cv2’
解决方案: 打开 cmd键入 pip install opencv_python -i https://pypi.tuna.tsinghua.edu.cn/simple...
SQL Server2008详细安装步骤(保姆式教程)
安装包下载 链接:https://pan.baidu.com/s/1Rjx4DHJBeCW2asC_4Kzo6Q?pwdchui 提取码:chui 安装过程 1.解压后使用管理员身份打开安装程序 2.选择全新安装或向现有安装添加新功能 3.确认 4.输入产品密钥(上方网盘安装包里有࿰…...
Linux常用操作
Linux常用操作 前言常用命令:一些操作命令:前言 本文是笔者在使用cadence的过程中,操作linux的笔记,仅记录个人常用,持续更新 常用命令: (1)高频:会了这几个就能在文件…...
Golang 处理parquet文件实战教程
Parquet是Apache基金会支持的项目,是面向列存储二进制文件格式。支持不同类型的压缩方式,广泛用于数据科学和大数据环境,如Hadoop生态。 本文主要介绍Go如何生成和处理parquet文件。 创建结构体 首先创建struct,用于表示要处理…...
腾讯TIM实现即时通信 v3+ts实践
目录 初始化sdk 功能描述 初始化 准备 SDKAppID 调用初始化接口 监听事件 发送消息 创建消息 创建文本消息 登录登出 功能描述 登录 登出 销毁 登录设置 获取会话列表 功能描述 获取会话列表 获取全量的会话列表 历史消息 功能描述 拉取消息列表 分页拉取…...
华为OD机试 - 回文字符串(Java JS Python)
题目描述 如果一个字符串正读和反渎都一样(大小写敏感),则称它为一个「回文串」,例如: leVel是一个「回文串」,因为它的正读和反读都是leVel;同理a也是「回文串」art不是一个「回文串」,因为它的反读tra与正读不同Level不是一个「回文串」,因为它的反读leveL与正读不…...
APP测试的7大注意点。
1. 运行 1) App安装完成后的试运行,可正常打开软件。 2) App打开测试,是否有加载状态进度提示。 3) App⻚面间的切换是否流畅,逻辑是否正确。 4) 注册 同表单编辑⻚面 用户名密码⻓度 …...
设计模式-第4章(装饰模式)
装饰模式装饰模型装饰模式示例商场收银程序(简单工厂策略装饰模式实现)装饰模式总结装饰模型 装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为…...
【算法设计-分治】快速幂与龟速乘
文章目录1. 快速幂2. 龟速乘3. 快速幂取模4. 龟速乘取模5. 快速幂取模优化1. 快速幂 算法原理: 计算 311: 311 (35)2 x 335 (32)2 x 332 3 x 3仅需计算 3 次,而非 11 次 计算 310: 310 (35)235 (32)2 x 332 3 x 3仅需计算…...
基于新一代kaldi项目的语音识别应用实例
本文是由郭理勇在第二届SH语音技术研讨会和第七届Kaldi技术交流会上对新一代kaldi项目在学术及“部署”两个方面报告的内容上的整理。如果有误,欢迎指正。 文字整理丨李泱泽 编辑丨语音小管家 喜报:新一代Kaldi团队三篇论文均被语音顶会ICASSP-2023接…...
【GO】31.grpc 客户端负载均衡源码分析
这篇文章是记录自己查看客户端grpc负载均衡源码的过程,并没有太详细的讲解,参考价值不大,可以直接跳过,主要给自己看的。一.主要接口:Balancer Resolver1.Balancer定义Resolver定义具体位置为1.grpc源码对解析器(resol…...
PTA L1-058 6翻了(详解)
前言:内容包括:题目,代码实现,大致思路,代码解读 题目: “666”是一种网络用语,大概是表示某人很厉害、我们很佩服的意思。最近又衍生出另一个数字“9”,意思是“6翻了”࿰…...
【Origin科研绘图】如何快速绘制一个折线图 ||【前端特效】爱心篇 之 幸好有你 || 泰坦尼克号——乘客生存与否 预测 || PyCharm使用介绍
🎯作者主页:追光者♂ 🌸个人简介:在读计算机专业硕士研究生、CSDN-人工智能领域新星创作者🏆、2022年CSDN博客之星人工智能领域TOP4🌟、阿里云社区专家博主🏅 【无限进步,一起追光!】 🍎欢迎点赞👍 收藏⭐ 留言📝 🌿本篇,首先是:基于科研绘图工具O…...
一文解读电压放大器(电压放大器原理)
关于电压放大器的科普知识,之前讲过很多,今天为大家汇总一篇文章来详细的讲解电压放大器,希望大家对于电压放大器能有更清晰的认识。电压放大器是什么:电压放大器是一种常用的电子器件,它的主要作用是把输入信号的振幅…...
线上监控诊断神器arthas
目录 什么是arthas 常用命令列表 1、dashboard仪表盘 2、heapdump dumpJAVA堆栈快照 3、jvm 4、thread 5、memory 官方文档 安装使用 1、云安装arthas 2、获取需要监控进程ID 3、运行arthas 4、进入仪表盘 5、其他命令使用查看官方文档 什么是arthas arthas是阿…...
@Import注解的原理
此注解是springboot自动注入的关键注解,所以拿出来单独分析一下。 启动类的run方法跟进去最终找到refresh方法; 这里直接看这个org.springframework.context.support.AbstractApplicationContext#refresh方法即可,它下面有一个方法 invoke…...
平台总线开发(id和设备树匹配)
目录 一、ID匹配之框架代码 二、ID匹配之led驱动 三、设备树匹配 四、设备树匹配之led驱动 五、一个编写驱动用的宏 一、ID匹配之框架代码 id匹配(可想象成八字匹配):一个驱动可以对应多个设备 ------优先级次低 注意事项…...
TS泛型,原来就这?
一、泛型是什么?有什么作用? 当我们定义一个变量不确定类型的时候有两种解决方式: 使用any 使用any定义时存在的问题:虽然知道传入值的类型但是无法获取函数返回值的类型;另外也失去了ts类型保护的优势 使用泛型 泛型…...
关于算法学习和刷题的建议
大家好,我是方圆。最近花时间学了学算法,应该算是我接触Java以来第一次真正的学习它,这篇帖子我会说一些我对算法学习的理解,当然这仅仅是浅浅的入算法的门,如果想深挖或者是有基础的人想提升自己,我觉得这…...
什么网站需要备案/十大广告投放平台
苹果cmsv10仿优酷模板在这里我的主题网需要提醒的是,不是1:1仿优酷的模板 只是首页的幻灯有些模仿,如果太多人都用仿优酷的模板不利于seo优化,有些差异化还是比较利于网站优化的。苹果cmsv10仿优酷模板1苹果cmsv10仿优酷模板演示地址…...
邀约网站怎么做请柬/草根seo视频大全
编码和解码 字符集和编码 常见编码规范 乱码的由来 解码过程:用一种编码方式将二进制码转换为字符 编码过程:用一种编码方式将字符转换为二进制码 URL的编码和解码 URL采用ASCII字符集进行编码,所以如果URL中包含非ASCII字符集中的字符&…...
克拉玛依市建设局官方网站/百度平台推广
GROUP BY GROUP BY语句用来与聚合函数(aggregate functions such as COUNT, SUM, AVG, MIN, or MAX.)联合使用来得到一个或多个列的结果集。 语法如下: SELECT column1, column2, ... column_n, aggregate_function (expression) FROM tables …...
wordpress 建立网站/国家免费技能培训官网
Java 构造结构私有化单例设计模式:(Singleton)在一般情况下,一个类只有通过产生对象之后才可以操作这个类。class Singleton {public void print() {System.out.println("Hello,world!") ;}}public class TestDemo {public static void main(S…...
wordpress怎么设置关键字/黄冈地区免费网站推广平台
为什么80%的码农都做不了架构师?>>> 前篇 JAVA多线程-基础Synchronized 后篇 JAVA多线程-交互计算 Future Callable Promise 后篇 JAVA多线程-线程池-实例模拟上厕所问题 跟上一篇文章比较,这次改进了之前的代码,使用了Lock Condition 和并发集合.代码…...
济南网站制作工作室/开发一个app需要多少钱?
在linux下,安装tomcat后发现执行shutdown.sh不好使,以前在执行weblogic脚本stopWeblogic也出现过此现象,原因可能是命令端口更改之类等各种原因的导致其失效,与其排查错误,不如干脆自己写个一劳永逸的脚本,…...