Coder Social home page Coder Social logo

zero2one's People

Contributors

qinziooo avatar skeanmy avatar

Watchers

 avatar  avatar

Forkers

qinziooo

zero2one's Issues

【React】React基础

React

  • 命令式开发:jq。
  • 声明式开发:react。节约dom操作
  • 多个框架可以共存,react只负责id是root的这部分
  • React是一个视图层框架,大型项目需要引入数据层框架
  • 函数式编程有利于代码的自动化测试

组件间通信

父组件向子组件的通信

单向数据流的props

子组件向父组件通信

  • 观察者模式:自定义事件机制
  • 调用回调:利用回调函数
    子组件向父组件通信,同样也需要父组件向子组件传递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函数采用了箭头函数的写法change = () => {},目的是为了改变this的指向。使得在函数单独调用的时候,函数内部的this依然指向child组件。
  • 如果不使用箭头函数,而是采用普通的写法则需要在子组件constructor中bind一下this.change = this.change.bind(this),或者在onClick方法中绑定this, onClick={this.change=this.change.bind(this)}。在构造函数中绑定this比较节约性能。
  • 父组件在通过props传递操作自己state的方法时,需要把这个方法的this牢牢的绑定在自己身上
    • 采用箭头函数的定义方式
    • 传递时进行bind

箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

state

  • 构造函数是唯一可以给 this.state 赋值的地方

    // 错误做法,不会重新渲染组件
    this.state.comment = 'Hello';
    // 正确做法
    this.setState({comment: 'Hello'});
    
    constructor(props) {
       super(props);
       this.state = {date: new Date()};
    }
  • state的更新是可能异步的,因为 this.propsthis.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进行操作
      })
    }

Ref

在典型的 React 数据流中,props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。

  • 使用ref之前思考一下状态提升是否可以解决问题
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 属性。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

【React】Redux

Redux

基础

产生原因:单页应用需要管理大量的状态

核心特点:

  • 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

    深浅拷贝复习
    1. 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

      浅拷贝只拷贝一层,对象拷贝其引用,基本类型拷贝其值

    2. 深拷贝:会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

    3. 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 } 达到相同的目的。

工作流程

img

Redux工作流程

store里面有需要的状态,就直接从store中取出来,没有的话会去reducer里面去找

  1. 写代码前先想一下state这个对象的结构,如何才能以最简的形式把应用的 state 用对象描述出来

  2. 用户发出 action,action是数据的唯一来源

    store.dispatch(action);
    • action

    • action函数:作用仅仅是简单的返回一个action,这样容易移植与测试

      // actionCreator.js
      import { CHANGE_INPUT } from './actionTypes';
      // 如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
      export const changeInputAction = (value)=>({
          type: CHANGE_INPUT,
          value
      })
  3. 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

    let nextState = todoApp(previousState, action);
    (previousState, action) => newState
  4. State 一旦有变化,Store 就会调用监听函数。

    // 设置监听函数
    store.subscribe(listener);
    function listerner() {
      let newState = store.getState();
      // component 就是 this
      component.setState(newState);   
    }

小坑

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
}

React-redux

image-20200106095737962

  • 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;

redux中间件—redux-thunk

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】webpack总结

webpack

  • --progress 构建进度
  • --watch 实时监测打包的文件,文件变化重新打包
  • --profile 编译过程中的步骤耗时时间
webpack-dev-server --open
--open 打开默认浏览器

分别介绍bundle,chunk,module是什么

  • bundle:是由webpack打包出来的文件
  • chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割
  • module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块

resolve

Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。

resolve:{
  alias:{
    components: './src/components/'
  }
}

当你通过 import Button from 'components/button 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button'

babel

  • presetsbabel插件集合

  • 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。

devServer

内存中打包,不设置publicPath时,默认将bundle.js输出到根目录,localhost:8000/bundle.js

webpack dev middleware原理

https://segmentfault.com/a/1190000005614604

课程学习

  • 安装webpack-cli会自动安装webpack,可以在命令行中使用webpack命令
  • npx webpack index.js
  • npx webpack -v:在当前目录查找webpack
  • npm info webpack:查看webpack存在的版本
  • package.json中的script中不用写npx,默认会在本地文件夹内先找对应的包

entry和output

  • 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

    • 上传到cdn:静态资源保存到cdn,可以把打包的html直接给后端去用

loader

loader的执行是从下到上,从右到左的执行

file-loader

  • 用于打包图片、普通文本等文件,打包普通图片时会把打包后文件的目录返回;

  • 通过options来增加配置

    name:通过placeholder占位符进行打包文件的重命名

    outputPath:配置打包的文件位置

  • 打包字体文件

    {
        test: /\.(eot|ttf|svg)$/,
        use: {
            loader: 'file-loader'
        } 
    }

url-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进行图片压缩配置

css-loader

import './style.css'

分析多个文件之间css的依赖关系

{
    loader: 'css-loader',
    options: {
        importLoaders: 2
    }
}

解决了这种场景:在一个scss文件中使用了@import './a.scss'时直接通过css-loader从而解析错误的情况,引入了options后所有引入的css也要走postcss-loaderscss-loader

style-loader

插入html头部的style标签

sass-loader

需要安装以下几个包

  • sass-loader
  • node-sass
{
    test: /\.scss$/,
    use: [
        'style-loader', 
        'css-loader', 
        'sass-loader',
        'postcss-loader'
    ]
}

postcss-loader

css3的样式需要写厂商前缀

// postcss.config.js
module.exports = {
  plugins: [
  	require('autoprefixer')
  ]
}

plugin

在webpack的生命周期之中自动做一些事情,在webpack打包后在控制台的输出日志可以看到插件的运行情况。

html-webpack-plugin

打包之后运行

会在打包结束后自动生成html文件,并把打包生成的js文件自动引入到html文件中

plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],

根据src目录下的模板去生成dist目录的模板

clean-webpack-plugin

打包之前运行

打包之前先把之前打包生成的文件删除

SourceMap

通过devtool进行配置

  • none:不开启
  • source-map:dist目录下生成一个map映射文件
  • inline-source-map:在打包生成的js文件最下面附上了映射文件
  • cheap-inline-source-map:原始的source-map比较耗费性能,加上cheap只精确到行,不精确到列
  • cheap-module-source-map:不仅仅关注业务代码,还会映射loader之类的代码,线上环境的最佳实践
  • eval:打包速度最快,通过eval这种执行js代码的形式进行代码映射,不适用于大型的应用代码
  • cheap-module-eval-source-map开发环境的最佳实践

webpackDevServer

  • webpack --watch 实时监测打包的文件,文件变化会重新打包
devServer: {
    // 服务器启动在哪个文件夹下面
    contentBase: './dist',
    open: true,
    port: 8080
},
  • 通过webpack-dev-middleware手动写一个devServer,可以在node中使用webpack,常用的是在命令行中使用
  • devServer打包出来的内容存放在内存中

实现请求转发

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

  • 底层的实现是connect-history-api-fallback

  • 只在本地有效,上线后需要后端进行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的功能
  • 处理一些数据文件时,需要自己写上面的功能

Tree Shaking

只打包引入的内容,别的没有使用的东西都不需要,通过“摇树”去除掉不需要的内容。

  • 只支持ES Module,因为它是一种静态的引入方式,CommonJs是动态引入方式;
// 开发环境的配置
optimization: {
    usedExports: true
},
// package.json
// 对所有的模块进行treeshaking
"sideEffects": false

// 对特定模块不进行treeshaking
"sideEffects": ["@babel/polyfill", "*.css"]
  • 开发环境下的treeshaking还是会打包没有引入的代码
  • 线上环境下的treeshaking就不会打包了

两种打包模式

使用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')
}

Code Spliting(代码分割)

公用代码的拆分

每次请求2Mb的资源一般来说要比并行加载两个1Mb要慢

// 同步代码分割
optimization: {
    splitChunks: {
        chunks: 'all'
    }
},

代码分割和webpack无关,在webpack中进行代码分割有两种方式:

  • 同步加载和异步加载都会进行代码分割
  • 异步代码(import)无需任何配置,自动进行代码分割
  • 同步代码在webpack配置文件中用optimization做代码分割

import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

打包分析,Preloading,Prefetch

Ctrl+Shift+p coverage

  • prefetch:有空闲加载
  • preload:和核心代码一起加载

缓存问题

在线上环境为了解决缓存的问题,需要对每一次打包的文件进行hash命名

Shimming(垫片)

webpack是基于模块打包的,模块之间不耦合。

如a模块引入了jquery,b模块是一个库,其中使用了jquery,在a中引入了b,会出现b中无法找到jq的情况。

模块内的this指向的是模块本身,不是window对象

库的打包

ts的打包

PWA的打包

EsLint的配置

  • 快速生成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
  }
};

webpack中配置eslint

  • 解决了开发者的编辑器不一定都能够安装eslint插件的痛点
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钩子进行优化,提交代码时才会进行代码检查

性能优化--提升打包速度

  • 跟上技术迭代:NodeNpmYarn升级

  • 在尽可能少的模块上应用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-loaderparallel-webpackhappypack多进程打包

  • 合理使用sourceMap

  • 结合stats分析打包结果,stats.json

  • 开发环境内存编译

  • 开发环境中剔除无用的插件

多页面打包(4-11)

  • 单页面应用:只有一个html文件

  • jqueryzepto等旧项目需要进行多页面打包

  • 使用html-webpack-plugin

实质是增加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;
}

自定义loader

  • 自定义loader时module.exports必须赋值给一个普通函数,不能时箭头函数,因为loader中需要操作this
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);
    }

loader能够实现的功能--解决源代码的包装问题

  • 代码异常的捕获,异常检测代码会侵入业务代码,即在业务代码中处理异常逻辑,代码就会显得比较乱。在loader中对代码进行AST分析,如果出现了function字段,就使用try...catch...将代码包裹起来
  • 支持国际化:利用node的全局变量判断打包的版本是中文版本还是英文版本,然后在loader中把全局中的占位符{{title}}替换成为对应的版本内容即可。

自定义plugin

  • 基于事件驱动模型,也就是发布订阅模式
  • plugin是webpack的灵魂,能够大大增强webpack的功能
  • 插件是一个类,Loader是一个函数
  • compiler是webpack的实例,有一些hooks,在某一特定时刻会自动执行。hooks
    • 同步钩子:compile,同步钩子没有回调方法cb
    • 异步钩子:emit
  • node调试,通过debugger打断点
    • npm run debug --> node --inspect --inspect-brk node_modules/webpack/bin/webpack.js
    • --inspect是启动node的调试工具
    • --inspect-brk指的是执行webpack.js时在文件的第一行打上断点
    • 打开chrome控制台就可以看到
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');

脚手架配置

CreateReactApp

  1. npm run eject:把隐藏的配置文件释放出来
  2. OneOf这个字段的配置是对特定后缀的文件进行准确匹配loader,仅仅一次,节约资源

VueCli

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的配置文件

【Typescript】泛型

TypeScript

  • 初始化: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'
});

【React】虚拟DOM

虚拟DOM

普通的数据变更带来的DOM更新过程:

  1. state数据
  2. JSX模板
  3. 数据 + 模板结合生成真实DOM,显示
  4. state改变
  5. 数据 + 模板结合生成真实DOM,并不直接替换原始的DOM
  6. 新DOM(DocumentFragment)和原始DOM做对比、找差异
  7. 找出input框发生变化
  8. 只用新DOM中input元素替换老DOM中的input元素

存在的问题:性能提升不明显

  1. state数据

  2. JSX模板

  3. 数据 + 模板结合生成真实DOM,显示

  4. 生成虚拟DOM,虚拟DOM是一个js对象,用于描述真实DOM,因此虚拟DOM的成本较低,可以适用于跨平台(提升性能的关键,js操作对象很容易,操作DOM就需要与浏览器底层api进行通信)

    <div id='abc'><span>hello world</span></div>
    const vdom = ['div', {id: 'abc'}, ['span', {}, 'hello world']]
  5. state发生变化

  6. 数据 + 模板生成新的虚拟DOM

    const vdom = ['div', {id: 'abc'}, ['span', {}, 'bye bye']]
  7. 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容的变化

  8. 直接操作DOM,改变span中的内容

React底层的实现是:先使用数据 + 模板生成虚拟DOM,再使用虚拟DOM结构生成真实DOM

JSX

  • JSX能够防止XSS攻击:React 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);

优势

  • 提升了性能
  • 使得跨端应用得以实现React Native
    • web应用:VDOM转换成DOM
    • 移动应用:VDOM转换成原生的组件

Diff算法

  • 同级比较,带来了算法时间复杂度的提升,牺牲了子DOM的浪费

    • 同层不一致直接替换
  • 没有key值的话需要两层循环判断新旧的对应关系,提高了复用性

    • key值不要设置为index
a 0 b 1 c 2
删除a后
b 0 c 1
匹配不上了

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.