skeanmy / zero2one Goto Github PK
View Code? Open in Web Editor NEW学习笔记的仓库
学习笔记的仓库
单向数据流的props
// 父组件App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
msg: '父类的消息',
name: 'John',
age: 99
}
}
callback = (msg, name, age) => {
// setState方法,修改msg的值,值是由child里面传过来的
this.setState({ msg });
this.setState({ name });
this.setState({ age });
}
render() {
return (
<div className="App">
<p> Message: {this.state.msg}</p>
<Child
callback={this.callback}
age={this.state.age}
name={this.state.name}>
</Child>
</div>
);
}
}
// 子组件Child
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Andy',
age: 31,
msg: "来自子类的消息"
}
}
change = () => {
this.props.callback(this.state.msg, this.state.name, this.state.age);
}
render() {
return (
<div>
<div>{this.props.name}</div>
<div>{this.props.age}</div>
<button onClick={this.change}>点击</button>
</div>
)
}
}
change = () => {}
,目的是为了改变this的指向。使得在函数单独调用的时候,函数内部的this依然指向child组件。this.change = this.change.bind(this)
,或者在onClick方法中绑定this, onClick={this.change=this.change.bind(this)}
。在构造函数中绑定this比较节约性能。箭头函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象。
构造函数是唯一可以给 this.state
赋值的地方
// 错误做法,不会重新渲染组件
this.state.comment = 'Hello';
// 正确做法
this.setState({comment: 'Hello'});
constructor(props) {
super(props);
this.state = {date: new Date()};
}
state的更新是可能异步的,因为 this.props
和 this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
让 setState()
接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数
修改state并且获取最新state的最佳方式
handleInputChange(e) {
// 异步操作state时,需要对这次的值进行保存
const { value } = e.target;
this.setState(() => ({
newValue:value
}), () => {
// 对最新的state进行操作
})
}
在典型的 React 数据流中,props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<div ref={this.myRef} />
<input ref={(ref) => {this.input = ref}}/>
);
}
}
ref
属性用于 HTML 元素时,构造函数中使用 React.createRef()
创建的 ref
接收底层 DOM 元素作为其 current
属性。ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其 current
属性。产生原因:单页应用需要管理大量的状态
核心特点:
state 这个对象没有修改器方法,即setter方法
要更改state中的数据,需要发起一个action,action是一些普通的js对象,用于描述发生了什么
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
action与state串起来即为reducer,即reducer是一个接收state与action,并且返回新的state的函数
三大特点:
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
让同构应用开发变得容易,来自于服务端的state可以很方便的序列化注入到客户端中
state是只读的,唯一改变state的方法是触发action
视图与网络请求不能直接修改state,只能表达要修改的意图,所有的修改被集中化处理,不会出现race condition
浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
浅拷贝只拷贝一层,对象拷贝其引用,基本类型拷贝其值
深拷贝:会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
Object.assign(target, ...sources);
Reducer必须是纯函数
如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。
reducer内不要做这些事情:
修改传入参数;
执行有副作用的操作,如 API 请求和路由跳转;
调用非纯函数,如 Date.now()
或 Math.random()
。
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
使用
Object.assign()
新建了一个副本。不能这样使用Object.assign(state, { visibilityFilter: action.filter })
,因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用{ ...state, ...newState }
达到相同的目的。
store里面有需要的状态,就直接从store中取出来,没有的话会去reducer里面去找
写代码前先想一下state这个对象的结构,如何才能以最简的形式把应用的 state 用对象描述出来
用户发出 action,action是数据的唯一来源
store.dispatch(action);
action
action函数:作用仅仅是简单的返回一个action,这样容易移植与测试
// actionCreator.js
import { CHANGE_INPUT } from './actionTypes';
// 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
export const changeInputAction = (value)=>({
type: CHANGE_INPUT,
value
})
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = todoApp(previousState, action);
(previousState, action) => newState
State 一旦有变化,Store 就会调用监听函数。
// 设置监听函数
store.subscribe(listener);
function listerner() {
let newState = store.getState();
// component 就是 this
component.setState(newState);
}
getState()
方法获取 state;dispatch(action)
方法更新 state;subscribe(listener)
注册监听器;subscribe(listener)
返回的函数注销监听器。todolist
删除时有问题
<List
bordered
dataSource={this.props.list}
renderItem={(item,index)=>(<List.Item onClick={(index)=>{this.props.deleteItem(index)}}>{item}</List.Item>)}
/>
主要是我们的index
出现了重新声明的问题。onClick
箭头函数接收到的参数是事件对象e
不是我们需要的index
<List
bordered
dataSource={this.props.list}
renderItem={(item,index)=>(<List.Item onClick={()=>{this.props.deleteItem(index)}}>{item}</List.Item>)}
/>
// index.js
import { createStore } from 'redux' // 引入createStore方法
import reducer from './reducer'
const store = createStore(reducer) // 创建数据存储仓库
export default store //暴露出去
// reducer.js
const defaultState = {} //默认数据
export default (state = defaultState,action)=>{ //就是一个方法函数
return state
}
Provider
提供器
只要使用了这个组件,组件里边的其它所有组件都可以使用store
了
connect
连接器
import {connect} from 'react-redux' //引入连接器
export default connect(xxx, null)(TodoList);
映射关系是把原来state映射成为组件中的props属性
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id)) // 派发一个actions,直通reducer(原理图)
}
};
};
const FilterLink = connect(
mapStateToProps, // 状态映射
mapDispatchToProps // 方法映射
)(Link);
export default FilterLink;
多做UI组件,即无状态组件
connect
的作用是把UI组件(无状态组件)和业务逻辑代码的分开,然后通过connect再链接到一起,让代码更加清晰和易于维护。这也是React-Redux
最大的优点。
import { connect } from 'react-redux';
import { setVisibilityFilter } from '../store/actions';
import Link from '../components/Link'; // UI组件
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
};
};
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link);
export default FilterLink;
dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware。
你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。换言之,中间件都是对store.dispatch()的增强
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
reducers,
applyMiddleware(thunk)
);
applyMiddleware
applyMiddleware
是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
const store = createStore(
reducers,
applyMiddleware(thunk, logger)
);
redux-thunk最重要的**,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。
正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。
//异步的action, 由于使用了redux-thunk的thunk中间件,
//所以,在这个action里不用像同步action那样,必须返回一个对象,在这里返回一个方法
//这个方法有一个dispatch的参数,这个dispatch就是store里的dispatch.
export const addAsync = (num = 1) => {
return (dispatch) => {
// 当这个方法刚进来的时候,就马上告诉reducer,我要开始获取数据了,
// reducer接收到这个动作,就会执行相应的操作(把isLoading改为true,看reducer里的代码)
dispatch({
type: START_GET_DATA
});
// 用setTimeout来模拟获取数据
setTimeout(() => {
// 获取数据完成之后,需要dispatch两个action.
// 一个是把获取到的数据,传到一个动作里(ADD)
dispatch({
type: ADD,
num
});
// 这个是用来告诉reducer,我获取数据成功了。reducer接收到这个动作,
// 就会执行相应的操作(把isLoading改为false,看reducer里的代码)
dispatch({
type: GET_DATA_SUCCESS
});
}, 2000)
}
}
webpack-dev-server --open
--open 打开默认浏览器
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。
resolve:{
alias:{
components: './src/components/'
}
}
当你通过 import Button from 'components/button
导入时,实际上被 alias
等价替换成了 import Button from './src/components/button'
。
presets
:babel
插件集合
babel-loader
:代码转换成es5
@babel/core
:核心库,必须有才能转,js转成抽象语法树AST
@babel/preset-env
:自动识别浏览器版本以及代码版本,然后找到对应的库去转
@babel/polyfill
:写业务代码时使用,补充缺失的对象方法等,如map、Promise对象等,在window对象上绑定了一系列的方法。
window.Promise
window.map
@babel/plugin-transform-runtime
:写库代码时使用,使用闭包形式引入es6的代码,打包第三方模块时使用,因为polyfill会污染全局变量
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: [
['@babel/preset-env', {
// 目标浏览器的版本号
targets: {
chrome: '67'
},
// 按需引入polyfill,如只写了map的语法,就不需要引入promise的polyfill
useBuiltIns: 'usage'
}]
]
}]
}
.babelrc
:把options中的内容写到这个文件中去
@babel/perset-react
:专门用于react
// .babelrc
{
presets: [
[
"@babel/preset-env", {
targets: {
chrome: "67",
},
useBuiltIns: 'usage'
}
],
"@babel/preset-react"
]
}
babel配置文件也是从下往上,从右往左执行;上面配置文件的含义是先解析react代码为es6,再将es6转换成es5。
内存中打包,不设置publicPath时,默认将bundle.js输出到根目录,localhost:8000/bundle.js
https://segmentfault.com/a/1190000005614604
webpack-cli
会自动安装webpack
,可以在命令行中使用webpack命令npx webpack index.js
npx webpack -v
:在当前目录查找webpacknpm info webpack
:查看webpack存在的版本package.json
中的script中不用写npx
,默认会在本地文件夹内先找对应的包entry默认打包输出的文件名称为main.js
output中可以对打包输出文件名进行更改
entry: {
main: './src/index.js',
sub: './src/index.js'
},
output: {
publicPath: 'http://cdn.com.cn',
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
在这种情况下,html-webpack-plugin会引入两个script文件
publicPath:添加打包出来文件的公共路径头,可以用于配置cdn
loader的执行是从下到上,从右到左的执行
用于打包图片、普通文本等文件,打包普通图片时会把打包后文件的目录返回;
通过options
来增加配置
name
:通过placeholder
占位符进行打包文件的重命名
outputPath
:配置打包的文件位置
打包字体文件
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}
可以实现file-loader
的所有功能
图片以base64
格式直接打包到对应的img标签上,节省了一次http请求
但是如果图片很大,bundle.js
就会很大,增加白屏时间
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}]
},
增加配置项limit
进行图片压缩配置
import './style.css'
分析多个文件之间css的依赖关系
{
loader: 'css-loader',
options: {
importLoaders: 2
}
}
解决了这种场景:在一个scss文件中使用了@import './a.scss'
时直接通过css-loader
从而解析错误的情况,引入了options
后所有引入的css
也要走postcss-loader
和scss-loader
。
插入html头部的style标签
需要安装以下几个包
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader'
]
}
css3
的样式需要写厂商前缀
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
在webpack的生命周期之中自动做一些事情,在webpack打包后在控制台的输出日志可以看到插件的运行情况。
打包之后运行
会在打包结束后自动生成html文件,并把打包生成的js文件自动引入到html文件中
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])],
根据src目录下的模板去生成dist目录的模板
打包之前运行
打包之前先把之前打包生成的文件删除
通过devtool进行配置
devServer: {
// 服务器启动在哪个文件夹下面
contentBase: './dist',
open: true,
port: 8080
},
proxy: {
// 这里可以写正则
'/react/api': {
target: 'https://www.dell-lee.com',
// 实现对https地址的转发
secure: false,
pathRewrite: {
'header.json': 'demo.json'
},
// bypass: 跳过的内容
// changeOrigin: 突破特定网站对origin的限制
changeOrigin: true,
headers: {
host: 'www.dell-lee.com',
// 也可以在这里配置cookie
}
}
}
devServer: {
historyApiFallback: true
}
// 等价于,即对任何路径的请求转发到index.html上,根据它上面的业务逻辑去决定显示什么样的内容
devServer: {
historyApiFallback: {
rewrites: [{
from: /\.*\/,
to: '/index.html'
}]
}
}
在react或者vue开发时,请求路径发生变化时,会把这个路径的请求转换成为对根路径\
的请求,而不会去请求服务器
如果服务器没有配置这个路由,则返回404
只在本地有效,上线后需要后端进行nginx
或者apache
去配置转发。
devServer: {
contentBase: './dist',
open: true,
port: 8080,
// 开启HMR功能
hot: true,
// 即使HMR不生效,浏览器也不自动刷新
// HMR失效时不刷新页面,即这个配置只对HMR负责
hotOnly: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
配置完HMR需要重启服务
// 如果开启了HMR功能
if(module.hot) {
// 如果number.js内容变化,则执行回调函数
module.hot.accept('./number', () => {
document.body.removeChild(document.getElementById('number'));
number();
})
}
css-loader
底层实现了css HMR的功能vue-loader
底层实现了vue文件HMR的功能只打包引入的内容,别的没有使用的东西都不需要,通过“摇树”去除掉不需要的内容。
// 开发环境的配置
optimization: {
usedExports: true
},
// package.json
// 对所有的模块进行treeshaking
"sideEffects": false
// 对特定模块不进行treeshaking
"sideEffects": ["@babel/polyfill", "*.css"]
使用webpack-merge
第三方模块进行webpack配置文件的合并
Development:开发环境的sourceMap很全,线上环境就不需要那么全了
Production:需要代码压缩
一般来说会创建一个build目录来对存放两个环境的配置文件,这时候打包出来的dist目录会位于build这个文件夹下面,为了解决这个问题,需要对配置文件中的有关于路径的地方进行更改
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
// 第一个参数数组中的路径是相对于根路径的路径
new CleanWebpackPlugin(['dist'], {
// 配置根路径
root: path.resolve(__dirname, '../')
})
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
公用代码的拆分
每次请求2Mb的资源一般来说要比并行加载两个1Mb要慢
// 同步代码分割
optimization: {
splitChunks: {
chunks: 'all'
}
},
代码分割和webpack无关,在webpack中进行代码分割有两种方式:
optimization
做代码分割import()
加载模块成功以后,这个模块会作为一个对象,当作then
方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
Ctrl+Shift+p coverage
在线上环境为了解决缓存的问题,需要对每一次打包的文件进行hash命名
webpack是基于模块打包的,模块之间不耦合。
如a模块引入了jquery,b模块是一个库,其中使用了jquery,在a中引入了b,会出现b中无法找到jq的情况。
模块内的this指向的是模块本身,不是window对象
eslint
配置npx eslint --init
eslint src
module.exports = {
"extends": "airbnb",
// 通用的eslint解析器
"parser": "babel-eslint",
"rules": {
"react/prefer-stateless-function": 0,
"react/jsx-filename-extension": 0
},
globals: {
document: false
}
};
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', 'eslint-loader']
}]
},
devServer: {
// 打开浏览器黑窗口报告错误
overlay: true,
},
eslint-loader
配置https://webpack.js.org/loaders/eslint-loader/#install
module.exports = {
entry: '...',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
cache: true,
},
force: 'pre'
}, 'babel-loader'
],
},
};
eslint-loader
一定要在babel-loader
前面执行,如果eslint-loader
放在了前面,需要配置force: pre
这个选项。eslint-loader
会降低打包速度git
钩子进行优化,提交代码时才会进行代码检查跟上技术迭代:Node
、Npm
、Yarn
升级
在尽可能少的模块上应用Loader
loader
里使用exclude
,减少额外的代码翻译include
限定语法转换的范围少使用Plugin
,确保插件的质量,多使用官方推荐的插件
resolve
参数合理配置
resolve: {
extensions: ['.js', '.jsx'],
// 配置目录下的入口文件
mainFiles: ['index', 'xxx'],
// 配置别名
alias: {
child: path.resolve(__dirname, '../src/a/b/c/child')
}
},
每一次resolve
都是文件查找操作,很耗时。
使用DllPlugin
提高打包速度
webpack.DllPlugin
webpack.DllReferencePlugin
打包好的dll文件要挂在到html上,注册一个全局变量,如react、jquery、loadsh等
控制包文件大小
thread-loader
,parallel-webpack
、happypack
多进程打包
合理使用sourceMap
结合stats
分析打包结果,stats.json
开发环境内存编译
开发环境中剔除无用的插件
单页面应用:只有一个html文件
jquery
、zepto
等旧项目需要进行多页面打包
实质是增加entry
内容,底下配合着添加HtmlWebpackPlugin
const makePlugins = (configs) => {
const plugins = [
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
];
Object.keys(configs.entry).forEach(item => {
plugins.push(
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item]
})
)
});
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
});
return plugins;
}
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
// 使用loader时会触发这个配置
resolveLoader: {
// 先去node_modules目录下面去找,再去loaders目录去找
modules: ['node_modules', './loaders']
},
module: {
rules: [{
test: /\.js/,
use: [
{
loader: 'replaceLoader',
},
{
loader: 'replaceLoaderAsync',
options: {
name: 'lee'
}
},
]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
通过this.query
进行传递参数,拿到options配置中的内容:this.query.name
通过this.callback
传递loader分析后的错误、SourceMap
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
处理异步逻辑时调用this.async()
方法
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const callback = this.async();
setTimeout(() => {
const result = source.replace('dell', options.name);
callback(null, result);
}, 1000);
}
AST
分析,如果出现了function
字段,就使用try...catch...
将代码包裹起来{{title}}
替换成为对应的版本内容即可。compile
,同步钩子没有回调方法cb
emit
debugger
打断点
npm run debug --> node --inspect --inspect-brk node_modules/webpack/bin/webpack.js
--inspect
是启动node的调试工具--inspect-brk
指的是执行webpack.js时在文件的第一行打上断点class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('compiler');
})
// compilation本次打包的内容
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
debugger;
compilation.assets['copyright.txt']= {
// 文件内容
source: function() {
return 'copyright by dell lee'
},
// 文件大小
size: function() {
return 21;
}
};
cb();
})
}
}
module.exports = CopyrightWebpackPlugin;
@babel/parser
分析源代码,把js代码转换成为一个js对象@babel/traverse
:遍历AST,对声明、表达式、模块引入等不同的语法进行识别,并且进行后续的操作const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
// 默认导出内容是esmodule的导出,需要加一个default
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');
const moduleAnalyser = (filename) => {
const content = fs.readFileSync(filename, 'utf-8');
// 把js代码转换成为一个js对象
const ast = parser.parse(content, {
// esmodule模式
sourceType: 'module'
});
const dependencies = {};
traverse(ast, {
// 有引入代码时走下面的内容
ImportDeclaration({ node }) {
const dirname = path.dirname(filename);
// 相对路径转绝对路径
const newFile = './' + path.join(dirname, node.source.value);
dependencies[node.source.value] = newFile;
}
});
// 使用babel进行代码转换
const { code } = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
});
return {
filename,
dependencies,
code
}
}
// 递归分析文件依赖
const makeDependenciesGraph = (entry) => {
const entryModule = moduleAnalyser(entry);
const graphArray = [ entryModule ];
for(let i = 0; i < graphArray.length; i++) {
const item = graphArray[i];
const { dependencies } = item;
if(dependencies) {
for(let j in dependencies) {
graphArray.push(
moduleAnalyser(dependencies[j])
);
}
}
}
const graph = {};
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
});
return graph;
}
// 闭包是最基础的
const generateCode = (entry) => {
const graph = JSON.stringify(makeDependenciesGraph(entry));
return `
(function(graph){
function require(module) {
function localRequire(relativePath) {
return require(graph[module].dependencies[relativePath]);
}
var exports = {};
(function(require, exports, code){
eval(code)
})(localRequire, exports, graph[module].code);
return exports;
};
require('${entry}')
})(${graph});
`;
}
const code = generateCode('./src/index.js');
npm run eject
:把隐藏的配置文件释放出来OneOf
这个字段的配置是对特定后缀的文件进行准确匹配loader,仅仅一次,节约资源npm install -g @vue/cli
# OR
yarn global add @vue/cli
# 检查版本
vue --version
vue create my-app
没有暴露webpack配置项,但是提供了一套简洁的api(vue.config.js)方便用户去配置webpack
实际上底层会把vue.config.js
中的内容翻译成webpack的配置
\node_modules\@vue\cli-service\lib\Service.js
把vue配置参数转换成为webpack的配置文件
tsc --init
,会在当前目录下生成一个tsconfig.json
tsc --version
或者tsc -v
ts-node
可以使得typescript
直接在node
环境下运行可以理解为不确定的类型,适用于需要复用的场景,而不需要对每一种类型都定义一种解决方法。
泛型函数类型前面有一个类型参数T
类型参数也可以有多个
可以使用多个泛型参数名
泛型函数支持对象字面量写法
function fn<T>(arg: T[]): T{}
// 非泛型函数的类型
(arg: any) => any
// 泛型函数的类型
<T>(arg: T) => T
// 多个类型参数
function fn3 <T,U> ( first:T, last:U ) {}
// 泛型函数支持对象字面量写法
function fn4 <T>(arg:T) : T{
return arg;
}
let Fn4: <P>(arg: P) => P = fn4;
let fn: { <T>(arg: T): T } = fn1;
// 任意函数的接口
interface Fn1Inter {
(arg: any): any
}
// 泛型函数的接口
interface Fn2Inter {
<T>(arg: T): T
}
// 泛型接口
interface Fn3Inter<T> {
(arg: T): T
}
function fn<T> (arg:T) : T{
return arg;
}
let Fn:Fn3Inter<string> = fn;
class InterClass<T> {
}
new InterClass<number>
// 一个求数组最小值的泛型类
class GetMin<T>{
arr: T[] = [];
add(ele: T) {
this.arr.push(ele);
}
min(): T {
let min = this.arr[0];
this.arr.forEach((value) => {
if (value < min) {
min = value;
}
})
return min;
}
}
let getMinNumber = new GetMin<number>();
getMinNumber.add(1);
getMinNumber.add(3);
getMinNumber.add(5);
console.log(getMinNumber.min());
泛型约束可以通过接口 + extends 来实现约束
// 会报错,T上没有length属性
function fn<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// 定义一个带有属性的接口
interface Len{
length : number;
}
// 对函数进行泛型约束
function fn<T extends Len>(arg: T): T {
console.log(arg.length);
return arg;
}
// 定义一个操作数据库的泛型接口
interface Db<T> {
insert(data: T): boolean;
delete(data: T, id: number): boolean;
update(data: T,id: number): boolean;
select(data: T,id: number): any[];
}
// 泛型类实现这个数据库接口
class Sql<T> implements Db<T> {
insert(data: T): boolean {
console.log(data);
return true;
};
delete(data: T, id: number): boolean {
return true;
};
update(data: T, id: number): boolean {
return true;
};
select(data: T, id: number): any[] {
return [1,2,3];
};
}
// 这个过程中接口和类的作用相同
class User{
userName:string;
userPwd:string;
}
interface User1{
userName:string;
userPwd:string;
}
let mySql = new Sql<User>();
mySql.insert({
userName:'admin',
userPwd:'123456'
});
普通的数据变更带来的DOM更新过程:
存在的问题:性能提升不明显
state数据
JSX模板
数据 + 模板结合生成真实DOM,显示
生成虚拟DOM,虚拟DOM是一个js对象,用于描述真实DOM,因此虚拟DOM的成本较低,可以适用于跨平台(提升性能的关键,js操作对象很容易,操作DOM就需要与浏览器底层api进行通信)
<div id='abc'><span>hello world</span></div>
const vdom = ['div', {id: 'abc'}, ['span', {}, 'hello world']]
state发生变化
数据 + 模板生成新的虚拟DOM
const vdom = ['div', {id: 'abc'}, ['span', {}, 'bye bye']]
比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容的变化
直接操作DOM,改变span中的内容
React底层的实现是:先使用数据 + 模板生成虚拟DOM,再使用虚拟DOM结构生成真实DOM
React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式(vue采用了这种方式),而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
// jsx语法
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// babel转义过
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
// 实际上创建的对象
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
// 后续去渲染真实DOM
React 元素是不可变对象(Immutable Object)。一旦被创建,你就无法更改它的子元素或者属性。更新 UI 唯一的方式是创建一个全新的元素,并将其传入ReactDOM.render()
。
// 计时器
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
同级比较,带来了算法时间复杂度的提升,牺牲了子DOM的浪费
没有key值的话需要两层循环判断新旧的对应关系,提高了复用性
a 0 b 1 c 2
删除a后
b 0 c 1
匹配不上了
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.