网站设计与制作专业软件销售具体怎么做的
网站设计与制作专业,软件销售具体怎么做的,山西教育学会的网站建设,自己做的网站怎样链接数据库框架设计远没有大家想的那么简单#xff0c;并不是说只把功能开发完成#xff0c;能用就算完事儿了#xff0c;这里面还是有很多学问的。比如说#xff0c;我们的框架应该给用户提供哪些构建产物#xff1f;产物的模块格式如何#xff1f;当用户没有以预期的方式使用框架… 框架设计远没有大家想的那么简单并不是说只把功能开发完成能用就算完事儿了这里面还是有很多学问的。比如说我们的框架应该给用户提供哪些构建产物产物的模块格式如何当用户没有以预期的方式使用框架时是否应该打印合适的警告信息从而提升更好的开发体验让用户快速定位问题开发版本的构建和生产版本的构建有何区别热跟新HMRHot Module Replacement需要框架层面的支持才行我们是否也应该考虑再有就是当你的框架提供了多个功能如果用户只需要其中几个功能那么用户是否可以选择关闭其他功能从而减少资源的打包体积所有以上这些问题我们都会在本节内容进行讨论。本节内容需要大家对常用的模块打包工具有一定的使用经验尤其是 rollup.js 以及 webpack。如果你只用过或了解过其中一个也没关系因为它们很多概念其实是类似的。如果你没有使用任何模块打包工具那么需要你自行去了解一下至少有了初步认识之后再来看本节内容会更好一些。提升用户的开发体验衡量一个框架是否足够优秀的指标之一就是看它的开发体验如何我们拿 Vue3 举个例子createApp(App).mount(#not-exist)
当我们创建一个 Vue 应用并试图将其挂载到一个不存在的 DOM 节点时就会得到一个警告信息warn从这条信息中我们得知挂载失败了并说明了失败的原因Vue 根据我们提供的选择器无法找到相应的 DOM 元素返回 null正式因为这条信息的存在使得我们能够清晰且快速的了解并定位问题可以试想一下如果 Vue 内部不做任何处理那么很可能得到的是一个 JS 层面的错误信息例如Uncaught TypeError: Cannot read property xxx of null但是根据此信息我们很难知道问题出在哪里。所以在框架设计和开发的过程中提供友好的警告信息是至关重要的如果这一点做得不好那么很可能经常收到用户的抱怨。始终提供友好的警告信息不仅能够快速帮助用户定位问题节省用户的时间还能够为框架收获良好的口碑让用户认为你是非常专业的。在 Vue 的源码中你经常能够看到 warn() 函数的调用例如上面图片中的信息就是由这句 warn() 函数调用打印的warn(Failed to mount app: mount target selector ${container} returned null.
)
对于 warn() 函数来说由于它需要尽可能的提供有用的信息因此它需要收集当前发生错误的组件的组件栈信息所以如果你去看源码你会发现有些复杂但其实最终就是调用了 console.warn() 函数。对于开发体验来说除了提供必要的警告信息还有很多其他方面可以作为切入口可以进一步提升用户的开发体验。例如在 Vue3 中当我们在控制台打印一个 Ref 数据时const count ref(0)
console.log(count)
打开控制台查看输出如下图所示没有任何处理的输出可以发现非常的不直观当然我们可以直接打印 count.value 这样就只会输出 0但是有没有办法在打印 count 的时候让输出的信息更有好呢当然可以浏览允许我们编写自定义的 formatter从而自定义输出的形式。在 Vue 的源码中你可以搜索到名为 initCustomFormatter 的函数这个函数就是用来在开发环境下初始化自定义 formatter 的以 chrome 为例我们可以打开 devtool 的设置然后勾选 Console - Enable custom formatters然后刷新浏览器后查看控制台会发现输出的内容变得非常直观控制框架代码的体积框架的大小也是衡量框架的标准之一在实现同样功能的情况下当然是用越少的代码越好这样体积就会越小最后浏览器加载资源的时间也就越少。这时我们不禁会想提供越完善的警告信息就意味着我们要编写更多的代码这不是与控制代码体积相驳吗没错所以我们要想办法解决这个问题。如果我们去看 Vue 的源码会发现每一个 warn() 函数的调用都会配合 __DEV__ 常量的检查例如if (__DEV__ !res) {warn(Failed to mount app: mount target selector ${container} returned null.)
}
可以看到打印警告信息的前提是__DEV__ 这个常量一定要为真这里的 __DEV__ 常量就是达到目的的关键。Vue 使用的是 rollup.js 对项目进行构建的这里的 __DEV__ 常量实际上是通过 rollup 的配置来预定义的其功能类似于 webpack 中的 DefinePlugin 插件。Vue 在输出资源的时候会输出两个版本的资源其中一个资源用于开发环境如 vue.global.js 另一个与其对应的用于生产环境如vue.global.prod.js 通过文件名称我们也能够区分。当 Vue 构建用于开发环境的资源时会把 __DEV__ 常量设置为 true这时上面那段输出警告信息的代码就等价于if (true !res) {warn(Failed to mount app: mount target selector ${container} returned null.)
}
可以看到这里的 __DEV__ 被替换成了字面量 true 所以这段代码在开发环境是肯定存在的。当 Vue 构建用于生产环境的资源时会把 __DEV__ 常量设置为 false这时上面那段输出警告信息的代码就等价于if (false !res) {warn(Failed to mount app: mount target selector ${container} returned null.)
}
可以看到 __DEV__ 常量被替换为字面量 false 这时我们发现这段分支代码永远都不会执行因为判断条件始终为假这段永远不会执行的代码被称为 Dead Code它不会出现在最终的产物中在构建资源的时候就会被移除因此在 vue.global.prod.js 中是不会存在这段代码的。这样我们就做到了在开发环境为用户提供友好的警告信息的同时还不会增加生产环境代码的体积。框架要做到良好的 Tree-Shaking上文中我们提到通过构建工具设置预定义的常量 __DEV__ 就能够做到在生产环境使得框架不包含打印警告信息的代码从而使得框架自身的代码量变少。但是从用户的角度来看这么做仍然不够还是拿 Vue 来举个例子我们知道 Vue 提供了内置的组件例如 Transition 如果我们的项目中根本就没有使用到该组件那么 Transition 组件的代码需要包含在我们项目最终的构建资源中吗答案是当然不需要那如何做到这一点呢这就不得不提到本节的主角 Tree-Shaking。那什么是 Tree-Shaking 呢在前端领域这个概念因 rollup 而普及简单的说所谓 **Tree-Shaking **指的就是消除哪些永远不会执行的代码也就是排除 dead-code现在无论是 rollup 还是 webpack 都支持 Tree-Shaking。想要实现 Tree-Shaking 必须满足一个条件即模块必须是 ES Module因为 Tree-Shaking 依赖 ESM 的静态结构。我们使用 rollup 通过一个简单的例子看看 Tree-Shaking 如何工作我们 demo 的目录结构如下├── demo
│ └── package.json
│ └── input.js
│ └── utils.js
首先安装 rollupyarn add rollup -D # 或者 npm install rollup -D
下面是 input.js 和 utils.js 文件的内容// input.js
import { foo } from ./utils.js
foo()
// utils.js
export function foo(obj) {obj obj.foo
}
export function bar(obj) {obj obj.bar
}
代码很简单我们在 utils.js 文件中定义并导出了两个函数分别是 foo 和 bar然后在 input.js 中导入了 foo 函数并执行注意我们并没有导入 bar 函数。接着我们执行如下命令使用 rollup 构建npx rollup input.js -f esm -o bundle.js
这句命令的意思是以 input.js 文件问入口输出 ESM 模块输出的文件名叫做 bundle.js 。命令执行成功后我们打开 bundle.js 来查看一下它的内容// bundle.js
function foo(obj) {obj obj.foo
}
foo();
可以看到其中并不包含 bar 函数这说明 Tree-Shaking 起了作用由于我们并没有使用 bar 函数因此它作为 dead-code 被删除了。但是如果我们仔细观察会发现foo 函数的执行也没啥意义呀就是读取了对象的值所以它执行还是不执行也没有本质的区别呀所以即使把这段代码删了也对我们的应用没啥影响那为什么 rollup 不把这段代码也作为 dead-code 移除呢这就涉及到 Tree-Shaking 中的第二个关键点即副作用。如果一个函数调用会产生副作用那么就不能将其移除。什么是副作用简单地说副作用的意思是当调用函数的时候会对外部产生影响例如修改了全局变量。这时你可能会说上面的代码明显是读取对象的值怎么会产生副作用呢其实是有可能的想想一下如果 obj 对象是一个通过 Proxy 创建的代理对象那么当我们读取对象属性时就会触发 Getter 在 Getter 中是可能产生副作用的例如我们在 Getter 中修改了某个全局变量。而到底会不会产生副作用这个只有代码真正运行的时候才能知道 JS 本身是动态语言想要静态的分析哪些代码是 dead-code 是一件很有难度的事儿上面只是举了一个简单的例子。正因为静态分析 JS 代码很困难所以诸如 rollup 等这类工具都会给我提供一个机制让我们有能力明确的告诉 rollup ”放心吧这段代码不会产生副作用你可以放心移除它“那具体怎么做呢如下代码所示我们修改 input.js 文件import {foo} from ./utils/*#__PURE__*/ foo()
注意这段注释代码 /*#__PURE_*_/该注释的作用就是用来告诉 rollup 对于 foo() 函数的调用不会产生副作用你可以放心的对其进行 Tree-Shaking此时再次执行构建命令并查看 bundle.js 文件你会发现它的内容是空的这说明 Tree-Shaking 生效了。基于这个案例大家应该明白的是在编写框架的时候我们需要合理的使用 /*#__PURE_*_/ 注释如果你去搜索 Vue 的源码会发现它大量的使用了该注释例如下面这句export const isHTMLTag /*#__PURE__*/ makeMap(HTML_TAGS)
也许你会觉得这会不会对编写代码带来很大的心智负担其实不会这是因为通常产生副作用的代码都是模块内函数的顶级调用什么是顶级调用呢如下代码所示foo() // 顶级调用function bar() {foo() // 函数内调用
}
可以看到对于顶级调用来说是可能产生副作用的但对于函数内调用来说只要函数 bar 没有被调用那么 foo 函数的调用当然不会产生副作用。因此你会发现在 Vue 的源码中基本都是在一些顶级调用的函数上使用 /*#__PURE__*/ 注释的。当然该注释不仅仅作用与函数它可以使用在任何语句上这个注释也不是只有 rollup 才能识别webpack 以及压缩工具如 terser 都能识别它。框架应该输出怎样的构建产物上文中我们提到 Vue 会为开发环境和生产环境输出不同的包例如 vue.global.js 用于开发环境它包含了必要的警告信息而 vue.global.prod.js 用于生产环境不包含警告信息。实际上 Vue 的构建产物除了有环境上的区分之外还会根据使用场景的不同而输出其他形式的产物这一节我们将讨论这些产物的用途以及在构建阶段如何输出这些产物。不同类型的产物一定是有对应的需求背景的因此我们从需求讲起。首先我们希望用户可以直接在 html 页面中使用 script 标签引入框架并使用bodyscript src/path/to/vue.js/scriptscriptconst { createApp } Vue// .../script
/body
为了能够实现这个需求我们就需要输出一种叫做 IIFE 格式的资源IIFE 的全称是 Immediately Invoked Function Expression 即”立即调用的函数表达式“可以很容易的用 JS 来表达(function () {// ...
}())
如上代码所示这就是一个立即执行的函数表达式。实际上 vue.globale.js 文件就是 IIFE 形式的资源大家可以看一下它的代码结构var Vue (function(exports){// ...exports.createApp createApp;// ...return exports
}({}))
这样当我们使用 script 标签直接引入 vue.global.js 文件后那么全局变量 Vue 就是可用的了。在 rollup 中我们可以通过配置 format: iife 来实现输出这种形式的资源// rollup.config.js
const config {input: input.js,output: {file: output.js,format: iife // 指定模块形式}
}export default config
不过随着技术的发展和浏览器的支持现在主流浏览器对原生 ESM 模块的支持都不错所以用户除了能够使用 script 标签引用 IIFE 格式的资源外还可以直接引如 ESM 格式的资源例如 Vue3 会输出 vue.esm-browser.js 文件用户可以直接用 script 标签引入script typemodule src/path/to/vue.esm-browser.js/script
为了输出 ESM 格式的资源就需要我们配置 rollup 的输出格式为format: esm。你可能已经注意到了为什么 vue.esm-browser.js 文件中会有 -browser 字样其实对于 ESM 格式的资源来说Vue 还会输出一个 vue.esm-bundler.js 文件其中 -browser 变成了 -bundler。为什么这么做呢我们知道无论是 rollup 还是 webpack 在寻找资源时如果 package.json 中存在 module 字段那么会优先使用 module 字段指向的资源来代替 main 字段所指向的资源。我们可以打开 Vue 源码中的 packages/vue/package.json 文件看一下{main: index.js,module: dist/vue.runtime.esm-bundler.js,
}
其中 module 字段指向的是 vue.runtime.esm-bundler.js 文件意思就是说如果你的项目是使用 webpack 构建的那你使用的 Vue 资源就是 vue.runtime.esm-bundler.js 也就是说带有 -bundler 字样的 ESM 资源是给 rollup 或 webpack 等打包工具使用的而带有 -browser 字样的 ESM 资源是直接给 script typemodule 去使用的。那他们之间的区别是什么呢那这就不得不提到上文中的 __DEV__ 常量当构建用于 script 标签的 ESM 资源时如果是用于开发环境那么 __DEV__ 会设置为 true如果是用于生产环境那么 __DEV__ 常量会被设置为 false 从而被 Tree-Shaking 移除。但是当我们构建提供给打包工具的 ESM 格式的资源时我们不能直接把 __DEV__ 设置为 true 或 false而是使用 (process.env.NODE_ENV ! production) 替换掉 __DEV__ 常量。例如下面的源码if (__DEV__) {warn(useCssModule() is not supported in the global build.)
}
在带有 -bundler 字样的资源中会变成if ((process.env.NODE_ENV ! production)) {warn(useCssModule() is not supported in the global build.)
}
这样用户侧的 webpack 配置可以自己决定构建资源的目标环境但是最终的效果其实是一样的这段代码也只会出现在开发环境。用户除了可以直接使用 script 标签引入资源我们还希望用户可以在 Node.js 中通过 require 语句引用资源例如const Vue require(vue)
为什么会有这种需求呢答案是服务端渲染当服务端渲染时 Vue 的代码是运行在 Node.js 环境的而非浏览器环境在 Node.js 环境下资源的模块格式应该是 CommonJS 简称 cjs。为了能够输出 cjs 模块的资源我们可以修改 rollup 的配置format: cjs 来实现// rollup.config.js
const config {input: input.js,output: {file: output.js,format: cjs // 指定模块形式}
}export default config
特性开关在设计框架时框架会提供诸多特性或功能给用户例如我们提供 A、B、C 三个特性给用户同时呢我们还提供了 a、b、c 三个对应的特性开关用户可以通过设置 a、b、c 为 true 和 false 来代表开启和关闭那么将会带来很多收益对于用户关闭的特性我们可以利用 Tree-Shaking 机制让其不包含在最终的资源中。该机制为框架设计带来了灵活性可以通过特性开关任意为框架添加新的特性而不用担心用不到这些特性的用户侧资源体积变大同时当框架升级时我们也可以通过特性开关来支持遗留的 API这样新的用户可以选择不适用遗留的 API从而做到用户侧资源最小化。那怎么实现特性开关呢其实很简单原理和上文提到的 __DEV__ 常量一样本质是利用 rollup 的预定义常量插件来实现那一段 Vue3 的 rollup 配置来看{__FEATURE_OPTIONS_API__: isBundlerESMBuild ? __VUE_OPTIONS_API__ : true,
}
其中 __FEATURE_OPTIONS_API__ 类似于 __DEV__我们可以在 Vue3 的源码中搜索可以找到很多类似如下代码这样的判断分支// support for 2.x options
if (__FEATURE_OPTIONS_API__) {currentInstance instancepauseTracking()applyOptions(instance, Component)resetTracking()currentInstance null
}
当 Vue 构建资源时如果构建的资源是用于给打包工具使用的话即带有 -bundler 字样的资源那么上面代码在资源中会变成// support for 2.x options
if (__VUE_OPTIONS_API__) { // 这一这里currentInstance instancepauseTracking()applyOptions(instance, Component)resetTracking()currentInstance null
}
其中 __VUE_OPTIONS_API__ 就是一个特性开关用户侧就可以通过设置 __VUE_OPTIONS_API__ 来控制是否包含这段代码。通常用户可以使用 webpack.DefinePlugin 插件实现// webpack.DefinePlugin 插件配置
new webpack.DefinePlugin({__VUE_OPTIONS_API__: JSON.stringify(true) // 开启特性
})
最后再来详细解释一下 __VUE_OPTIONS_API__ 开关是干嘛用的在 Vue2 中我们编写的组件叫做组件选项 APIexport default {data() {}, // data 选项computed: {}, // computed 选项// 其他选项...
}
但是在 Vue3 中更推荐使用 Composition API 来编写代码例如export default {setup() {const count ref(0)const doubleCount computed(() count.value * 2) // 相当于 Vue2 中的 computed 选项}
}
但是为了兼容 Vue2在 Vue3 中仍然可以使用选项 API 的方式编写代码但是对于明确知道自己不会使用选项 API 的用户来说它们就可以选择使用 __VUE_OPTIONS_API__ 开关来关闭该特性这样在打包的时候 Vue 的这部分代码就不会包含在最终的资源中从而减小资源体积。错误处理错误处理是开发框架的过程中非常重要的环节框架的错误处理做的好坏能够直接决定用户应用程序的健壮性同时还决定了用户开发应用时处理错误的心智负担。为了让大家对错误处理的重要性有更加直观的感受我们从一个小例子说起。假设我们开发了一个工具模块代码如下// utils.js
export default {foo(fn) {fn fn()}
}
该模块导出一个对象其中 foo 属性是一个函数接收一个回调函数作为参数调用 foo 函数时会执行回调函数在用户侧使用时import utils from utils.js
utils.foo(() {// ...
})
大家思考一下如果用户提供的回调函数在执行的时候出错了怎么办此时有两个办法其一是让用户自行处理这需要用户自己去 try...catchimport utils from utils.js
utils.foo(() {try {// ...} catch (e) {// ...}
})
但是这对用户来说是增加了负担试想一下如果 utils.js 不是仅仅提供了一个 foo 函数而是提供了几十上百个类似的函数那么用户在使用的时候就需要逐一添加错误处理程序。第二种办法是我们代替用户统一处理错误如下代码所示// utils.js
export default {foo(fn) {try {fn fn() } catch(e) {/* ... */}},bar(fn) {try {fn fn() } catch(e) {/* ... */}},
}
这中办法其实就是我们代替用户编写错误处理程序实际上我们可以进一步封装错误处理程序为一个函数假设叫它 callWithErrorHandling// utils.js
export default {foo(fn) {callWithErrorHandling(fn)},bar(fn) {callWithErrorHandling(fn)},
}
function callWithErrorHandling(fn) {try {fn fn()} catch (e) {console.log(e)}
}
可以看到代码变得简洁多了但简洁不是目的这么做真正的好处是我们有机会为用户提供统一的错误处理接口如下代码所示// utils.js
let handleError null
export default {foo(fn) {callWithErrorHandling(fn)},// 用户可以调用该函数注册统一的错误处理函数resigterErrorHandler(fn) {handleError fn}
}
function callWithErrorHandling(fn) {try {fn fn()} catch (e) {// 捕获到的错误传递给用户的错误处理程序handleError(e)}
}
我们提供了 resigterErrorHandler 函数用户可以使用它注册错误处理程序然后在 callWithErrorHandling 函数内部捕获到错误时把错误对象传递给用户注册的错误处理程序。这样在用户侧的代码就会非常简洁且健壮import utils from utils.js
// 注册错误处理程序
utils.resigterErrorHandler((e) {console.log(e)
})
utils.foo(() {/*...*/})
utils.bar(() {/*...*/})
这时错误处理的能力完全由用户控制用户既可以选择忽略错误也可以调用上报程序将错误上报到监控系统。实际上这就是 Vue 错误处理的原理你可以在源码中搜索到 callWithErrorHandling 函数另外在 Vue 中我们也可以注册统一的错误处理函数import App from App.vue
const app createApp(App)
app.config.errorHandler () {// 错误处理程序
}
良好的 Typescript 类型支持Typescript 是微软开源的编程语言简称 TS它是 JS 的超集能够为 JS 提供类型支持。现在越来越多的人和团队在他们的项目中使用 TS 语言使用 TS 的好处很多如代码即文档、编辑器的自动提示、一定程度上能够避免低级 bug、让代码的可维护性更强等等。因此对 TS 类型支持的是否完善也成为评价一个框架的重要指标。那如何衡量一个框架对 TS 类型支持的好坏呢这里有一个常见的误区很多同学以为只要是使用 TS 编写就是对 TS 类型支持的友好其实使用 TS 编写框架和框架对 TS 类型支持的友好是两件关系不大的事儿。考虑到有的同学可能没有接触过 TS所以这里不会做深入讨论我们只举一个简单的例子如下是使用 TS 编写的函数function foo(val: any) {return val
}
这个函数很简单它接受一个参数 val 并且参数可以是任意类型any该函数直接将参数作为返回值这说明返回值的类型是由参数决定的参数如果是 number 类型那么返回值也是 number 类型然后我们可以尝试使用一下这个函数如下图所示类型支持不友好在调用 foo 函数时我们传递了一个字符串类型的参数 str按照之前的分析我们得到的结果 res 的类型应该也是字符串类型然而当我们把鼠标 hover 到 res 常量上时可以看到其类型是 any这并不是我们想要的结果为了达到理想状态我们只需要对 foo 函数做简单的修改即可function fooT extends any(val: T): T {return val
}
大家不需要理解这段代码我们直接来看一下现在的表现类型友好可以看到 res 的类型是字符字面量 str 而不是 any 了这说明我们的代码生效了。通过这个简单的例子我们认识到使用 TS 编写代码与对 TS 类型支持友好是两件事在编写大型框架时想要做到完美的 TS 类型支持是一件很不容易的事情大家可以查看 Vue 源码中的 runtime-core/src/apiDefineComponent.ts 文件整个文件里真正会在浏览器运行的代码其实只有 3 行但是当你打开这个文件的时候你会发现它整整有接近 200 行的代码其实这些代码都是在做类型支持方面的事情由此可见框架想要做到完善的类型支持是需要付出相当大的努力的。除了要花大力气做类型推导从而做到更好的类型支持外还要考虑对 TSX 的支持我们会单独一篇来详细讨论。以上欢迎分享、关注。最近组建了一个江西人的前端交流群如果你也是江西人可以加我微信ruochuan12 拉你进群。常驻推荐阅读若川知乎高赞有哪些必看的 JS库我在阿里招前端我该怎么帮你现在还可以加模拟面试群如何拿下阿里巴巴 P6 的前端 Offer如何准备阿里P6/P7前端面试--项目经历准备篇大厂面试官常问的亮点该如何做出如何从初级到专家(P4-P7)打破成长瓶颈和有效突破若川知乎问答2年前端经验做的项目没什么技术含量怎么办常驻末尾你好我是若川江西人~(点击蓝字了解我)历时一年只写了一个学习源码整体架构系列 有哪些必看的JS库jQuery、underscore、lodash、sentry、vuex、axios、koa、redux关注若川视野回复pdf 领取优质前端书籍pdf回复1可加群长期交流学习我的博客地址https://lxchuan12.gitee.io 欢迎收藏觉得文章不错可以 分享、点赞、在看 呀^_^另外欢迎留言交流小提醒若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮欢迎点击阅读也可以星标我的公众号便于查找
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/90346.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!