前端入门 - 工具篇
响应式系统与React
Reacet的历史与应用
- 前端应用开发,如Facebook, Instagram,Netflix 网页版。
- 移动原生应用开发(React代码->native代码),如Instagram,Discord,Oculus。
- 结合 Electron(结合了node),进行桌面应用开发。
- react three fiber 用来写3D图形的库
React的历史
2010 年 Facebook 在其 php 生态中,引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来的 React 的设计。
(写php的时候可以写一个组件片段(声明式,类似HTML),在别的地方也可以写一个组件片段,然后可以把两个组件片段像两个变量一样拼在一起。声明式的写法。)
2011 年 Jordan Walke 创造了 FaxJS,也就是后来的 React 原型。
2012 年 在 Facebook 收购 Instagram 后,该 FaxJS 项目在内部得到使用,Jordan Walke 基于 FaxJS 的经验,创造了 React。
2013 年 React 正式开源,在 2013 JSConf 上 Jordan Walke 介绍了这款全新的框架。
2014年 - 今天 生态大爆发,各种围绕 React 的新工具/新框架开始涌现
React 的设计思路
用原生JS写UI的痛点:
- 状态(自己声明的变量)更新,UI不会自动更新,需要手动地调用DOM进行更新。
- 欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
- UI之间的数据依赖关系(当某些东西改变的时候,其它东西也要跟着改变),需要手动维护,如果依赖链路长,则会遇到"Callback Hell"
监控系统(响应式系统):监控火灾:当火灾发生之后,要监听到这个事件,当事件发生之后,要做一些响应(报警,疏散人群)
对上述组件划分的总结
- 1.组件是 组件的组合/原子组件
- 2.组件内拥有状态,外部不可见
- 3.父组件可将状态传入组件内部(对外暴露的接口,外部可以传进来,内部可以消费)
[当前价格]状态属于谁? 只能属于Root节点!!!
状态上升:状态归属于两个节点向上寻找到最近的祖宗节点。
思考:
-
1.React 是单向数据流,还是双向数据流?
单向数据流,永远只能父组件给子组件传东西。但这并不代表子组件不能改变父组件的状态。
-
2.如何解决状态不合理上升的问题 ?
-
3.组件的状态改变后,如何更新 DOM ?
组件设计:
-
1.组件声明了状态和 UI的映射。
输入几个状态,返回一个UI
-
2.组件有 Props(外)/State(内) 两种状态,
组件内部有自己的状态,外部不可见。
同时外部的父组件可以给子组件传状态。
-
3.“组件”可由其他组件拼装而成。
组件代码:
- 1.组件内部拥有私有状态 State。
- 2.组件接受外部的 Props 状态提供复用性。
- 3.根据当前的 State/Props,返回一个 UI。
组件的生命周期:
React(hooks)的写法
useEffect: 传入一个函数,和一个数组,数组是状态的数组,称作依赖项,该函数在 mount时,和依赖项被 set 的时候会执行。
- 有“副作用”的函数,要传入 useEffect来执行。
- 副作用代表除了单纯的计算之外,还要做其它的一些事情,比如网络请求,更新DOM,localStorage存储数据等。
Hook使用法则
- 不要在循环,条件或者嵌套函数中调用Hook。
React 的实现
Problems
- JSX 不符合 JS 标准语法
-
返回的 JSX 发生改变时,如何更新 DOM
Virtual DOM(虚拟 DOM)
真实的DOM不是js中的对象,而是一个在浏览器内部维护的状态,只能通过DOM接口修改DOM。
Virtual DOM 是一种用于和真实 DOM 同步,而在 JS 内存中维护的一个对象,它具有和 DOM 类似的树状结构,并和DOM 可以建立一一对应的关系。
它赋予了 React 声明式的 API:您告诉 React 希望让 UI是什么状态,React 就确保 DOM 匹配(自动更新)该状态。这使您可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。
-
指令式: 需要手动的告诉程序该怎么做
-
声明式: 发出指令
-
响应式: 声明式编程的一个类别,当某个状态发生改变时,自动的更新UI,自己响应自己
-
-
-
Heuristic算法
-
不同类型的元素 替换
- image span div 整个子树都要被替换
-
同类型的DOM元素 更新
- 调用DOM接口的setattribute元素
-
同类型的组件元素 递归
-
- State/Props 更新时,要重新触发 render 函数(就是组件函数本身)
React 状态管理库
会降低组件的复用性,和外部的Store强耦合了。
把所有的节点看成一个大的UI组件,Store是大的UI组件的State。
哪些东西放在状态管理库:
思考这个状态是被整个APP所拥有的还是被某个组件所拥有的?
字节做的全栈开发:
import {useModel} from '@modern-js/runtime/model'
// 共享xy状态
const Subcomponent = props =>{
const [{x,y}] = useModel(countModel);
return (
<h1>
{x} and {y}
</h1>
)
}
const [{x,y},{incrementX,incrementY}] = useModel(countModel);
<button type="button" onClick={()=>{incrementX()}}>
x + 1
</button>
<button type="button" onClick={()=>{incrementY()}}>
y + 1
</button>
应用级框架科普
Vite知识体系
浅谈构建工具
Vite概要介绍
定位: 新一代前端构建工具
两大组成部分
1.No-bundle 开发服务,
源文件无需打包
2.生产环境
基于 Rollup 的 Bundler
核心特征
1.高性能,dev 启动速度和热更新速度非常快!
2.简单易用,开发者体验好
Why Vite:
问题:
缓慢的启动 ->项目编译等待成本高 缓慢的热更新 ->修改代码后不能实时更新
瓶颈: bundle带来的性能开销 JavaScript语言的性能瓶颈(单线程解释性语言)
- 浏览器原生ESM支持
- 基于原生ESM的开发服务优势
Vite Dev Server底层原理就是type="module"
- 基于Esbuild的编译性能优化
- 内置的web构建能力
Vite上手实战
1.项目初始化
npm i -g pnpm
pnpm create vite
vite_project
react
react-ts
cd vite_project // 切换目录
pnpm install // 安装依赖
npm run dev // 启动项目
2.接入Sass/Scss & CSS Modules
pnpm install sass -D
在src下新建一个components/Header/index.tsx
// index.tsx
import React from "react";
export function Header(){
return <div>Header</div>
}
然后在src/App.tsx下引入<Header></Header>
// 在components/Header/新建文件index.module.scss
// 把.module.scss结尾的文件当作cssmodule文件来处理
.header{
color: red;
}
//然后在index.tsx中引入样式
import React from "react";
import styles from './index.module.scss'
export function Header(){
return <div className={styles.header}>Header</div>
}
3.使用静态资源
react.svg
4.HMR(Hot Module Replacement)
热更新 + 状态保留
5.生产环境 Tree Shaking
把代码里面没用的删除掉
// 在src下新建util.ts
export const add = {a:number,b:number}:number => a+b;
export const multi = {a:number,b:number}:number => a*b;
// 然后在src/components/Header/index.tsx中使用
import React from "react";
import styles from './index.module.scss'
import {add} from "../../util"
export function Header(){
return <div className={styles.header}>Header{add(1+2)}</div>
}
// build:tsc && vite build
// tsc的编译:为了进行项目的类型检查
// vite build打包
// 可在dist/assets下的文件找到打包后的文件
// 没有用到multi,所以multi会被删掉
优化原理:
1.基于 ESM 的 import/export 语句依赖关系,与运行时状态无关
2.在构建阶段将未使用到的代码进行删除
Vite
- 响应迅速
- 开箱即用
Vite整体架构
预打包
单文件编译
代码压缩
插件机制(最重要)
Vite进阶路线
深入双引擎
- esbuild esbuild.github.io/
- rollup.js
推荐学习顺序 先了解基本使用,动手尝试各项常用配置; 然后学习其插件开发。
Vite插件开发
- 抽离核心逻辑
- 易于拓展
参考资料:
Vite插件开发文档cn.vitejs.dev/guide/api-p…
复杂度较低的插件:json加载插件github.com/vitejs/vite…
复杂度中等的插件:Esbuild接入插件github.com/vitejs/vite…
复杂度较高的插件:官方React插件
前端必须知道的开发调试知识
PC端
-
Elements
-
styles 动态修改元素和样式
-
点击 .cls 开启动态修改元素的 class
-
输入字符串可以动态的给元素添加类名
-
勾选/取消类名可以动态的查看类名生效效果
-
点击具体的样式值(字号、颜色、宽度高度等)可以进行编辑,浏览器内容区域实时预览
-
Computed 下点击样式里的箭头可以跳转到 styles面板中的 css 规则
-
-
-
Console
控制台,可以在里面去打字,打印出代码里面的日志。
console.log("Welcome to ByteDance!"); console.warn('Welcome to ByteDance!'); console.error('Welcome to ByteDance!'); console.debug('Welcome to ByteDance!'); console.info('Welcome to ByteDance!'); console.log('%s %o,%c%s','hello', { name: 'Tom', age: 18 }, 'font-size:24px;color:red', 'Welcome to ByteDance!'); // Corrected goods array const goods = [ { name: 'apple', count: 300, price: 10 }, { name: 'banana', count: 110, price: 4 }, { name: 'orange', count: 30, price: 7 } ];
注意:不同类型的值输出的颜色是不一样的。
- console.table()
- console.dir()
-
console.time()
-
Sources
展示项目的源代码
- 第一种调试方法:debugger 断点调试
- 第二种调试方法:
-
scope 作用域(闭包)
-
Call Stack 调用堆栈
-
XHR/fetch breakpoints XHR/提取断点
一旦程序发生了网络请求,就会进入断点
-
DOM breakpoints
当HTML中某一个元素发生变化的时候,可以添加一个断点
压缩后的代码如何调试?
前端代码天生具有“开源”属性,出于安全考虑,上线之前JavaScript 代码通常会被压缩,压缩后的代码只有一行,整体变得不可阅读。
开启source-map的方法:devtools: 'source-map'
-
如果代码出错,点击console中error最右边,就可以跳转到源码。
-
可以看到错误对应的源码位置,然后去进行修改。
-
思考题:既然 Source Map 可以映射源码,那压缩后的代码带上 Source Map 上线不就又不安全了吗?
是的,带上Source Map打包会产生2个文件,其中一个文件是包含源码的。可以把该文件上传到监控平台,然后把该文件从dist里面删掉,这样就可以安全上线了。
-
Network
-
Application
清除缓存
-
Performance
官方提供的demo:
FPS
- Lighthouse
移动端
真机调试
VConsole
使用代理工具调试
常用代理工具
- Charles:适合查看、控制网络请求,分析数据
- Fiddler:与 Charles 类似, 适合 windows 平台
- spy-debugger:远程调试手机页面,抓包
- Whistle: 基于 Node 实现的跨平台 Web 调试代理工具
Nodejs调试
可以直接使用官方提供的方法
1.结合浏览器
2.VScode 运行->添加配置->启动调试->添加断点
常用开发调试技巧
线上即时修改 在调试DOM元素或样式的时候经常使用到的一个技巧
(现在更习惯用热更新,这种方法可能就不那么通过了
利用代理解决开发阶段的跨域问题
启用本地source map
线上不存在 source Map 时可以使用Charles中的 Map Local网络映射功能来访问本地的 source Map 文件。
- 应用上线的时候由于我们删除了包含源代码的文件,不过我们本地还是可以再npm run build一下就又有该文件了
- 再去访问运行代码,出错之后就可以找到哪里出错了。
- 线上没有source Map,所以映射到本地,就可以进行访问了。
使用代理工具Mock数据
操作就是,点击Mock数据然后点Map local
构建Webpack知识体系
为什么要学习Webpack?
- 理解前端“工程化”概念、工具、目标
- 一个团队总要有那么几个人熟悉 Webpack,某种程度上可以成为个人的核心竞争力
- 高阶前端必经之路
目标:
- 理解 Webpack 的基本用法
- 通过介绍 Webpack 功能、Loader 与 Plugin 组件设计,建立一个知识体系
什么是Webpack?
使用Webpack
- 1.安装依赖->
- 2.写配置文件->
- 3.执行编译命令
1.入口处理 开始读entry文件,启动编译流程
2.依赖解析 从entry文件中开始找到依赖'require' 'import' (import bar from './bar'这就是依赖语句)
3.资源解析 根据module配置,把一些非js资源转成js
4. 第二步第三步都执行完之后,资源合并打包,最终再生成一个JS文件
2找到的新的资源 那么就要进行第3步,所以2,3是递归调用,知道所有资源处理完毕
模块化+一致性
模块化:支持对不同类型的资源都用import,require这些语句去管理,也支持多个资源的合并打包,从而减少http请求数。
配置文件
“配置”大致分为两类,
- 流程类:作用于前面说的四个步骤的
- 工具类:主流程之外,提供更多工程化能力的配置项
学习Webpack
eg1.start
console.log('hello ${bar}')会被编译成1行
因为mode会被设置成默认的"production",产物会做一个压缩,会简洁很多。
eg2.处理css
// index.js
import './index.css';
console.log('hello world')
// index.css
.main{
font-size: 10px
}
// 需要定义一个loader
// 用module定义loader,用的时候更关注rules
module:{
rules:[{ // 在rules会有一个数组,有test和use2个属性
test:/.css$/, // 过滤条件,满足test规则的采用rules去处理
use:['style-loader','css-loader'] // 用什么样的loader去处理满足test条件的文件
}]
}
最后css会被转译成500多行
问题:
- Loader 有什么作用?为什么这里需要用到 css-loader、style-loader
- 与旧时代 -- 在 HTML 文件中维护 css 相比,这种方式会有什么优劣处?
- 有没有接触过 Less、Sass、Stylus 这一类 CSS 预编译框架?如何在 Webpack 接入这些工具?
eg3.处理js
接入babel,用于js转译
// webpack.config.js
module:{
rules:[{
// 对js文件用babel-loader去处理,同时还有一个options,这个options最终会被传入babel-loader里
// preset 规则集
test:/.js$/,
use:[{
loader : 'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}]
}]
}
问题:
- Babel 具体有什么功能?
- Babel 与 Webpack 分别解决了什么问题?为何两者能协作到一起了?
eg4.处理html
// index.js
console.log('hello world')
// 把index.js通过<script/>导入index.html
// 在webpack环境下,可以不用写html文件,直接生成。
// webpack.config.js
const path = require('path')
// 通过HTMLWebpackPlugin插件自动的生成一个html文件
const HTMLWebpackPlugin = require('html-webpack-plugin')
modules.exports = {
// 没有声明任何loader,直接用new HTMLWebpackPlugin()去调用
entry : './src/index',
mode : 'development',
devtools : false,
output : {
filename : '[name].js',
path : path.join(__dirname, './dist')
},
plugins : [new HTMLWebpackPlugin()]
}
问题:
- 相比于手工维护 HTML 内容,这种自动生成的方式有什么优缺点?
使用Webpack—工具线
eg5.HMR: Hot Module Replacement-- 模块热替换
写的代码能够马上呈现在页面上,不需要刷新,直接就能应用新的代码。
还需要一个watch:true会持续监听文件的变化。
eg5.Tree-Shaking: 树摇,用于删除Dead Code
本质上是用来删除一些没有用到的代码。
开启tree-shaking:
-
mode:production
-
optimization:{
usedExports:true
}
至此,为期接近一个月的青训营结束,不过我的前端生活才正式步入正轨。明确了学习方向,在这一个月的时间里,着实做到了入门。感谢字节跳动提供的机会!!!寒假的青训营我还是会参加。以后在校期间只要有青训营,那么我就肯定不会缺席!!!
之后的时间先复习一下学过的这么多知识,然后尽量写一个复盘的笔记,能够让我以后再看的时候也能立马想到学了什么,怎么用等等。
复盘差不多之后开始做项目,本次项目是一个LLM对话框组件。还是很有意思的,如果能够做出来想必也会很实用,在日后的简历中也是一个亮点!加油!!!