Code Monkey home page Code Monkey logo

blog's People

Contributors

wuyuanlijie avatar

Stargazers

 avatar

Watchers

 avatar

blog's Issues

Webpack打包优化🚀

Webpack打包优化

Webpack 打包优化——体积

定位webpack体积大的原因

推荐使用webpack-bundle-analyzer--Webpack插件和CLI的实用程序。将内容展示为方便交互的直观树状图。

1、引入DLLPlugin、DllReferencePlugin

DllPlugin(打包后,user的配置文件)、Dll(打包后存放位置)提供了大幅度提高构建时间性能的方式拆分软件包的方法。原理:将特定的第三方NPM包模块提前构建,然后通过页面引入。不仅可以使得vendor文件大幅度减小,同时也极大的提高了构建的速度。

  • Webpack Dll功能:避免第三方库在每次打包的时候,刷新vendor代码(发布新的版本,不需要重新打包第三方库)
    dll 包就是一个纯粹的依赖库,它本身是不能执行的,用来给app引用的。将所有的包含的库的索引,写在一个manifest文件中,我们在引用这个库的时候,只需要读取这个文件即可。
  • 优点:
  1. dll打包后独立存在,只要其包含的库没有增、减,hash不会变化,因此线上的dll的代码不会随着版本的发布频繁更新。
  2. App部分修改后,只需要编译app部分的代码,dll不需要重新打包。
  3. 多个项目之间,如果使用相同的依赖库,就可以共用一个dll。
    我们需要去建立一个dll的配置文件。entry,只包含第三方库。

2、外部引入模块(CDN)

浏览器的兼容问题,我们需要去使用babel转换,从而需要去引入babel-polyfill以确保兼容,之外还有moment、lodash都是很大的包,我们可以考虑外部的去引入文件,使用externals指定,webpack就可以使其不参与打包,但是依然可以在代码中通过CMD、AMD或者window/global全局的方式引入

// webpack 中予以指定
externals: {
  'babel-polyfill': 'window'
}

<script src="//cdn.bootcss.com/babel-polyfill/7.0.0-alpha.15/polyfill.min.js"></script>

3. 让每个第三包“引用所值”

  • 确定引入的必要性
  • 避免类库引而不用
  • 尽量使用模块化的引入
import _ from 'lodash'

export default {
  cloneDeep: _.cloneDeep,
  debounce: _.debounce,
  throttle: _.throttle,
  size: _.size,
  pick: _.pick,
  isEmpty: _.isEmpty
}

  • 尽可能的引入更合适的包

4. 按需异步加载模块

/** Vue **/
import Foo from './Foo.vue';

// 改写
const Foo = () => import('./Foo.vue')

这样该组件的所依赖的其他的组件或者其他的模块,都会被自动的分割进对应的chunk里,实现异步加载,也支持把组件按组分块,将同组中组件,打包在同一个异步的chunk中。
在React里面我们,可以将路由按需加载,使用bundle-loader。

5. 生产环境,压缩混淆并且移除console

现在的中等的规模的开发,都要区分开发环境、测试环境、生产环境。我们可以在生产环境,压缩js文件以有效的减小包的体积,同时移除使用比较频繁的console

new webpack.optimize.UglifyJsPlugin({\
    compress: {
        warning: false,
        drop_console: true,
        pure_funcs: ['console.log']
    },
    sourceMap: false
})

Webpack打包优化——速度

1. 减少文件搜索范围

在使用实际项目开发中,为了提升开发效率,很明显你会使用很多成熟第三方库;即便自己写的代码,模块间相互引用,为了方便也会使用相对路径,或者别名(alias);这中间如果能使得 Webpack 更快寻找到目标,将对打包速度产生很是积极的影响。于此,我们需要做的即:减小文件搜索范围,从而提升速度;实现这一点,可以有如下两法:

  • 配置resolve.modules
    Webpack的resolve.module配置模版库(node_moduels)所在的位置,在js出现的import 'vue'的这样的做法不是相对、绝对路径的写法,会去node_modules目录下查找。默认的配置,会采用向上递归搜索的方式去寻找,但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;同样,对于别名(alias)的配置,亦当如此
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
resolve: {
    // extensions 自动解析确定的扩展(不需要去待扩展名 import File from '../path/file')
    extensions: ['.js', '.vue', '.json'],
    
    // modules 告诉webpack解析模块时候应该搜索的目录。
    modules: [
      resolve('src'),
      resolve('node_modules')
    ],
    
    // alias创建import或者require的别名,来保证模块引入更简单
    // 确保模块的直接的引入 import store from 'store';
    alias: {
      'vue$': 'vue/dist/vue.common.js',
      'src': resolve('src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components'),
      // ...
      'store': resolve('src/store')
    }
  },
  • 设置test&include&exclude

2. 增强代码压缩的工具

webpack默认提供UglifyJS插件,由于采用单线程压缩,速度颇慢。推荐采用webpack-parallel-uglify-plugin插件,使用UglifyJS插件,更加充分而合理的使用CPU资源,大大减少构建时间。

3. 使用Happypack来加速代码的构建

  • 我们所认识的wenpack为了方便各种资源和类型的加载,设计了loader加载器的形式读取资源,受限制于nodejs的模型影响,所有的loader虽然async形式并且调用,但是还是在运行在单个node的线程中,以及在同一个事件循环中。当同时加载多个loader文件资源的时候,需要耗费大量的cpu运算的过程,node单线程毫无优势
  • Happy处理的思路是:将原有的webpack对loader的执行过程,从单一的线程形式扩展为多进程模式,从而加速代码的构建。原本的流程保持不变,这样可以在不修改原有配置的基础上,来完成对编译过程的优化。还是使用之前的loaders方法

4. 设置babel的cacheDirectory为true

babel-loader很慢,所以不仅要使用exclude、include,尽可能准确的指定要转化内容的范畴,而且要充分利用缓存,进一步提升性能。babel-loader 提供了 cacheDirectory特定选项(默认 false):设置时,给定的目录将用于缓存加载器的结果。
未来的 Webpack 构建将尝试从缓存中读取,以避免在每次运行时运行潜在昂贵的 Babel 重新编译过程。如果值为空(loader: ‘babel-loader?cacheDirectory’)或true(loader: babel-loader?cacheDirectory=true),node_modules/.cache/babel-loader 则 node_modules 在任何根目录中找不到任何文件夹时,加载程序将使用默认缓存目录或回退到默认的OS临时文件目录。

rules: [
  {
    test: /\.js$/,
    loader: 'babel-loader?cacheDirectory=true',
    exclude: /node_modules/,
    include: [resolve('src'), resolve('test')]
  },
  ...
]

5. 设置noParse

如果你确定一个模块中,没有其他的新的依赖,我们就可以配置这个。Webpack将不在扫描这个文件中的依赖。这对于比较大型类库,将能促进性能表现。

module: {
  noParse: /node_modules\/(element-ui\.js)/,
  rules: [
    {
      ...
    }
}

6. 拷贝静态文件

在前面Webpack 打包优化之体积篇,引入 DllPlugin 和 DllReferencePlugin 来提前构建一些第三方库,来优化 Webpack 打包。而在生产环境时,就需要将提前构建好的包,同步到 dist 中;这里拷贝静态文件,你可以使用 copy-webpack-plugin 插件:把指定文件夹下的文件复制到指定的目录。

React在工作中的使用总结

1.React组件的生命周期:

React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁

图片

1. 初始化阶段

  • getDefaultPorps

设置默认的props,也可以用来设置组件的的默认属性,只调用一次

  • getInitialState

初始化组件state的值,其返回值会赋值给组件的this.state的属性,只调用一次

  • componentWillMount

该方法在完成渲染之前调用,在这里可以对组件的state最后的一次修改机会。

  • render

创建虚拟DOM,进行diff算法,更新dom树来在这里进行,此时就不能修改state

  • componentDidMount

render方法成功调用,并且真实的DOM都已经被渲染。在这里可以对数据进行请求

2. 更行的阶段

  • componentWillReceiveProps:

nextProps 代表从父组件获取到新的props——组件接受到新的props,会触发该函数,父组件的状态改变,给子组件传入新的prop值。用于组件的props变化后,更新state。

  • shouldComponentUpdate

react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候)

  • componentWillUpdate

组件初始化不调用,组件需要更新的时候调用,此时可以修改state

  • render
  • componentDidUpdate

组件初始化不调用,组件更新完成后调用,此时可以获取dom节点

3. 销毁的阶段

  • componentWillUnmount

组件将要卸载的时候调用,一定事件监听、定时器需要在这里清除。

2. webpack-dev-server与react-hot-loader两者的热更新替换有什么区别?

  • webpack-dev-server自己的--hot模式只能即时的刷新的页面,但状态保持不住,因为会重新的去刷新页面
  • react-hot-loader是在--hot的基础上做了额外的处理,来保证状态可以存下来,支持react组件状态不会丢失。根据stateNode节点的更新对比,只更新改变的reactDOM节点,从而保留了未改变的state的值。

react-hot-loader开启的流程:

  1. 启动webpack-dev-server命令:1、添加--inline。2、在webpack.config.js添加devServer: {inline: true}; 当配置中没有--inline选项的时候,我们需要在entry里面添加webpack-dev-server/client?http://localhost:port
  2. webpack-dev-server启动inline模式、且允许热更新hot,在plugins添加HotModuleReplacementPlugin()
  3. 在entry的入口entry的参数,添加react-hot-loader/patch
  4. 入口文件index.js 引入react-hot-loader的AppContainer组件,在这个组件下的所有的子组件都会发生变化,才会触发热更新。

3. React RouterV4 在react中的使用

React RouterV4遵循了React的理念:万物皆组件。
React RouterV4基于Learn管理多个Repository,其代码库包括:

  1. react-router React Router的核心
  2. react-router-dom 用于DOM绑定的React Router
  3. react-router-native 用于React Native的React Router
  4. react-router-redux React Router和Redux的集成
  5. react-router-config 静态路由的配置帮组助手

4. this.setState更新的问题

  • this.setState是异步的,所以在this.setState之后立即调用this.state是获取不到最新的数据的。

下面介绍三种方法来获取最新的数据:
1.回调函数

this.setState({
  val: this.state.val + 1
}, () => {console.log(this.state.val)});

2.利用componentDidUpdate,因为在这个生命周期函数,说明页面已经渲染完成state、props都已经被渲染好了

componentDidUpdate(){
  console.log(this.state.val);
}

3.将this.setState放入到setTimeout函数中(因为在this.setState之后this.state是立即更新的!)

let self = this;
setTimeout(function(){
  self.setState({
    val: this.state.val + 1
  });
  console.log(self.state.val);
})

5. this.props.children容器类组件

  • 容器内组件,顾名思义就是里面的什么内容都不确定,只要负责装东西,它只是定义了外层结构形式,我们可以往里面塞任意的内容。
  • 在使用的自定义组件的时候,我们可以在里面嵌套jsx代码。嵌套的结构在组件的内部可以通过props.children获取。
  • 如果你在父组件的内部添加一个函数,在当前的组件中就可以通过this.props.children获取到这个函数(自己传入参数,但是函数是从父类传过来的)相当于vue的slot
  • this.props.chuldren是一个数组(它在当前的组件里面),是可以获取从父组件需要展示的列表项的内容!!!。
  • this.props 代表从父组件中获取到的内容

6. Props和State

  • React的核心**就是组件化的**,组件根据props和state两个参数,计算得到对应的界面的UI。可见,props和state是组件两个重要的数据来源。
  • props是组件对外的接口,state是组件对内的接口。组件内部可以引用其他的组件,组件之间的引用就形成了一个树状的结构(组件树)。组件的props数据,上层组件就可以通过下层组件的props属性进行传递。组件自身的也需要维护组件的数据,这就是对内的state。

如何设置一个组件state?

state必须能代表一个组件的UI呈现的完整状态集,组件对应UI的任何变化,都可以从state变化中反映出来,同时,state还必须是代表一个组件UI呈现的最小状态集。

  1. 变量是通过props从父组件获取的,则不是一个状态。
  2. 变量在组件的整个生命周期都保持不变的,则不是一个状态。
  3. 变量是可以通过state或props的已有数据获取得到的,则不是一个状态。
  4. 变量不在组件render中使用,则不是一个状态。这个变量更适合定义为组件的一个普通的属性。

如何正确的修改state

  1. 不能直接修改state

直接修改state,组件不会重新触发render

  1. state的更新是异步的

setState只是把修改的状态放入一个队列中,React会优化真正的时机,并且React会出于性能的问题,可能会多次将setState的状态修改合并成一次状态的修改。props的更新也是异步的。例如当我们连续点击两次按钮,连续调用两次this.setState,在React合并多次修改为一次的情况下,相当执行了以下的代码。后面的操作会覆盖前面的操作,最终的购买数量就只增加1个。

// 错误 这里只会触发一个添加的事件
Object.assign(
   previousState,
  {quantity: this.state.quantity + 1},
  {quantity: this.state.quantity + 1}
)
// 正确 接收一个函数作为参数的setState、第一个参数是组件修改组件前的state,后面是组件最新的props
this.setState((preState, props) => {
  counter: preState.quantity + 1,
})

3.state更新是一个浅合并(Shallow Merge)的过程

当调用setState修改组件的时候,只需要传入发生改变的状态变量,而不是组件完整的state,因为组件的的更新是一个浅合并的过程。只需要修改我们需要去修改的即可。

React建议我们把state当作一个不可变的对象,

  1. 状态的类型是不可变类型(数字、字符串、布尔值、null、undefined)
  2. 状态的类型是数组
    如有一个数组类型的状态books,当向books中增加一本书时,使用数组的concat方法或ES6的数组扩展语法(spread syntax)
// 方法一 concat
this.setState(preState => ({  // 返回一个对象 return {...}
  books: preState.books.concat(['React Book'])   // concat用于连接一个或多个数组。
}))
// 方法二 ES6 展开运算符
this.setState(preState => {
  books: [...preState.books, 'React Book']
}))

还可以使用slice来截取数组,使用filter来过滤数组,在setState里面参数添加一个函数,函数的参数是修改前的state。(concat、filter、slice返回一个新的数组,主要不要使用push、pop、shift、unshift、splice等方法修改数组,因为它们是在原数组的基础上修改的。)
3. 状态的类型是简单的对象
修改state中对象的方法的时候,以下的两种方式:

this.state = {
  owner = {
    name: '李杰',
    age: 20
  }
}
// 使用ES6的Object.assign方法
this.setState(preState => ({
    // Object.assign用于将所有可枚举属性的值从一个或多个源对象复制到目标对象
    owner: Object.assign({}, preState.owner, {name: 'lijie'})
  })
)
// 对象的展开运算符
this.setState(preState => ({
    owner: {...preState.owner, name: 'lijie'}
  })
)

=> React建议state不可变对象,所以当组件某个状态发生改变的时候,我们应该重新去创建一个新的状态,而不是修改原来的状态。避免重新去直接修改原对象的方法。

为什么React推荐组件的状态不可变对象?

  • 一是方便管理和调试
  • 二是出于性能的考虑,组件的状态是不可变的对象的时候,我们在组件的shouldComponentUpdate的方法中,仅需要比较状态的引用就可以判断状态是否改变,从而避免了不必要的render方法的调用。

JavaScript执行机制

JavaScript事件循环

任务可以分为两种,一种是同步任务(synchronous),另外一种是异步任务(asynchronous)。

  • 同步任务指的是,在主线程上排队执行的任务,只有当前一个任务执行完毕,才能执行后一个任务;
  • 异步任务指的是,不进入主线程、而进入“任务队列”的任务
  • 一旦当前“执行栈”中所有的同步任务都执行完毕,系统就开始读取“任务队列”。
  • 那么如何判断主线程执行栈为空?
    JS引擎存在于主线程中,会持续不断的检查主线程执行栈是否为空,一旦为空,就会从Event Queue那里检查是否有等待被调用的函数。主线程从“任务队列”中读取事件,整个过程是循环不断的,所以
    整个这种运行机制又成为Event Loop(事件循环)。

宏任务&微任务

  • Marco-tasks(宏任务):setTimeout&setInterval、MessageChannel、postMessage、setImmediate、I/O、UI rendering、requestAnimationFrame、包括整体的script代码
  • Micro-tasks(微任务):post.nextTick、Process.then、MutationObserver、Object.observe(废弃)
  • 不同的类型的任务会进入对应的任务队列。在任务队列中,在每一次事件循环中,marco-task只会提取一个来交给主线程执行,而mirco-task则会一直提取,知道micro-task的队列为空为止。
  • 进入整体代码(宏任务)后,开始第一次循环。执行整体代码,接着执行所有的微任务,至此,第一次循环结束。然后再找到宏任务的一个任务队列执行完毕,再执行所有的微任务,第二次循环结束。
  • 注意:new Promise()是同步的,但是promise.then是异步的
  • microtask队列中的任务都在当前轮中运行,而marcotask队列中的任务必须等待下一次事件循环。
  • 这个两个任务的区别:在于执行的时机。macrotask在一次主线程执行栈结束后浏览器进行渲染,然后才执行下一个marcotask;但是microtask会影响IO回调。
  • microtask >> macrotask ==> setTimeout(4ms延迟)是最后的执行的

实际来说,microtask并非每次在主栈末尾才执行,如果一个函数执行完毕之后,栈空了,那么microtask也会执行。

为什么用microtask?

在每个task(macrotask)运行完后,UI都会重新渲染,那么microtask中就完成数据的更新,因此当前task结束就可以得到最新的UI了。反之,如果新建一个task(macrotask)来做数据更新的话,那么渲染会执行两次。

这两种队列的执行顺序?

  1. 浏览器先执行一个macrotask(script代码),执行完主执行线程中的任务
  2. 然后会将microtask所有的任务执行完毕。
  3. 接着取出macrotask中的下一个任务。
  4. 重复2、3操作
  5. 最终所有的队列都清空,代码执行完毕。

HTML Document&Location

Document对象

每个载入浏览的HTML文档都会成为Document对象,Document对象使得我们可以从脚本中对HTML页面中所有的元素进行方法,Document对象是Window的一部分,window.document

Document对象方法

  • body:提供了对body元素的直接访问
  • cookie:设置或者返回当前文档有关的所有的cookie
  • domain:返回文档的域名
  • lastModified:返回文档被最后修改的日期和时间
  • referrer:返回载入当前文档的URL
  • title:返回当前文档的标题
  • URL: 返回当前的文档的URL

Location对象

window.location对象用于获得当前页面的地址URL,并把浏览器重定向到新的页面

  • hostname:返回web主机的域名
  • pathname:返回当前页面的路径和文件名
  • port:返回web主机的端口
  • protocol:返回当前所使用的web协议(http/https)
  • href:属性返回当前页面的URL(得到完整的链接)
  • hash:得到的是锚链接
  • reload():方法用于重新加载当前文档

React-Router v4

这里使用的react-router-dom这个API 理念:万物皆是组件 react-router-dom用于DOM绑定的React Router

Provider是用来保持与store的更新,Router是用来保持与loaction的同步 (Router的角色类似与Provider)

Router组件下只允许存在一个子元素,可以包裹一个div,或者只使用一个Route

Route组件主要的作用的就是当一个location匹配一个路由的path,渲染某些UI,

  • 组件有以下的属性:path路由的匹配路径; exact为true的时候,则要求路径必须与location.pathname完全的匹配。
  • strict为true的时候,有结尾的斜线的路径只能匹配有斜线的loaction.pathname (包括斜线后面的部分)

Route中的三种渲染的方法:

  1. component,在地址匹配的时候React组件才会被渲染,route props也会随着一起被渲染 (优先级高于render)
  2. render 这种对于内联渲染和包装的组件却不会引起意料之外的重现挂载 (包装组件,例如调用重定向,参数是一个函数,返回一个数组)
  3. children 和render的工作方式基本相同,除了它是不管地址匹配与否会被调用。
  • 、:是的特定的版本,会在匹配上的当前url时候给已经渲染的元素添加样式参数。
  • 该组建用来渲染匹配地址的第一个或者,独特之处它仅仅渲染一个路由!!

JS函数节流throttle和防抖debounce

函数节流和防抖

  • 都是优化高频率执行js代码的的一种手段

1. 函数节流

  允许一个函数在一个规定的时间内只执行一次。比如人眨眼睛,就是一定的时间内眨眼一次。

// 1、函数节流 (声明一个变量当标志位,记录当前的代码是否在执行!)
let canRun = true;
document.getElementById('throttle').onscroll = () => {
  if (!canRun) {
    return;  // 判断是否已经空闲,如果在执行的过程中了,那么就直接return;
  }
  canRun = false;
  setTimeout(function() {
    console.log('函数节流,可以在这里执行');
    canRun = true; // 判断是否能执行 这个时候函数已经执行完了 所以可以进行执行了
  }, 300);
}

2. 函数防抖

  1. 指的是频繁触发的情况下,只要有足够的空闲时间,才执行一次代码。(等待人陆续上完车后,司机就不会开车,只有没人刷卡了,司机才会开车)。
  2. 最常见的就是用户的时候的手机验证和邮箱验证,只有等用户输入完毕后,前端才能检测格式是否正确。
  3. 要点是:需要一个setTimeout来辅助实现,延迟需要跑的代码。
let timer = null;
document.getElementById('debounce').onscroll = () => {
  clearTimeout(timer); // 如果多次的触发代码,就用clearTimeout清除掉,重现开始(就像只有输入邮箱完毕后,才能去执行检测格式是否正确的代码)
  timer = setTimeout(function() {
    console.log('函数防抖!'); // 如果在300ms后,没有滚动了,我们可以执行里面的代码。setTimeout做一个缓冲池
  }, 300)
}

React-Router v4 使用踩坑

React-Router

这里使用的react-router-dom这个API 理念:万物皆是组件 react-router-dom用于DOM绑定的React Router

  • Provider是用来保持与store的更新,Router是用来保持与loaction的同步 (Router的角色类似与Provider)
  • Router组件下只允许存在一个子元素,可以包裹一个div,或者只使用一个Route
  • Route组件主要的作用的就是当一个location匹配一个路由的path,渲染某些UI,
  • 组件有以下的属性:
    path路由的匹配路径;
    exact为true的时候,则要求路径必须与location.pathname完全的匹配。
    strict为true的时候,有结尾的斜线的路径只能匹配有斜线的loaction.pathname (包括斜线后面的部分)

Route中的三种渲染的方法:

  1. component,在地址匹配的时候React组件才会被渲染,route props也会随着一起被渲染 (优先级高于render)
  2. render 这种对于内联渲染和包装的组件却不会引起意料之外的重现挂载 (包装组件,例如调用重定向,参数是一个函数,返回一个数组)
  3. children 和render的工作方式基本相同,除了它是不管地址匹配与否会被调用。
<Link>、<NavLink>:是<Link>的特定的版本,会在匹配上的当前url时候给已经渲染的元素添加样式参数。
<Switch> 该组建用来渲染匹配地址的第一个<Route>或者<Redirect>,独特之处它仅仅渲染一个路由!!
<Redirect from="/" to="/home"/> 说明路由的重定向

使用history来控制路由

1.使用withRouter高阶组件(官方推荐,不适合在redux使用)

import React from "react";
import {withRouter} from "react-router-dom";

class MyComponent extends React.Component {
  ...
  myFunction() {
    this.props.history.push("/some/Path");
  }
  ...
}
export default withRouter(MyComponent);

2.使用Context(不建议使用,react不推荐使用context)

v4在Router组件中通过Context暴露了一个router对象,在子组件中使用Context,可以获取router对象

import React from "react";
import PropTypes from "prop-types";

class MyComponent extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  }
  constructor(props, context) {
     super(props, context);
  }
  ...
  myFunction() {
    this.context.router.history.push("/some/Path");
  }
  ...
}

3.hack(推荐使用)

自己去创建history

// src/history.js
import createHistory from 'history/createBrowserHistory';

export default createHistory();

// src/index.js
import { Router, Link, Route } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      ...
    </Router>
  </Provider>,
  document.getElementById('root'),
);

Redux学习笔记

Redux核心概念

1. 使用普通对象来描述应用的State

{
  todo: [
      {
        text: 'learn React',
        complete: true,
      },
      {
        text: 'Exercise',
        complete: false,a
      },
  ],
  visibilityFilter: 'SHOW_COMPLETED'
}
这个对象就是“Model”,区别是并没有setter(修改器),其他的代码不能去随意修改它。

2. 想要更新state中的数据,我们需要发起一个Action。

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }

  强制使用action来描述所有的变化带来的好处就是可以清晰的知道应用中到底发生了什么。action就是来描述里面发生了什么的面包屑。最终为了将action和state连接起来,开发一些函数,这就是reducer。
  Action是把数据从应用传到store的有效载荷,是store数据的唯一来源。通过store.dispatch()将action传到store。当规模的很大的时候,我们单独的模块存放action。type会定义成字符串常量(actionTypes)

区别Action和Action Creator:

  • Action Creator就是一个创建action的函数/工厂
  • Action是一个信息的负载。

3. 连接action和state,使用Reducer

fucntion visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
        return action.filter;
    case 'ADD_TODO':
        return state.concat(action.payload);
  }
}
  • reducer只是一个接收state和action的函数,action中存在type、payload(新的state)属性,进行处理后,返回新的state函数。
  • 再开发出一个reducer来调用这两个reducer,进而来管理整个应用的state。
  • 主要的想法就是根据这些action对象来更新state
  • Reducers指定了应用如何响应actions并且发送到store。actions只是描述了有些事情的发生了这个事实,并没有描述应用如何去更新state。(actions是说明了发生了哪些变化)
  • 保持reducer纯净很重要,不要做一下的操作
    • 修改传入的参数;
    • 执行有副作用的操作,如API请求、路由跳转(api请求放在action中去处理)
    • 调用非纯函数,如Date.now()、Math.random()
  • 学习去使用ES6的对象的结构
  • 拆分Reducer,每个Reducer只负责管理全局的state中它负责的一部分。每个reducer的state参数都不同,分别对应它管理的那部分state数据。

4. Store(state、dispatch)

  • 前面action主要描述发生了什么,reducers来根据action更新state。
  • Store就是将它们联系在一起的对象,有以下的职责:
<li>维持应用的state;</li>
<li>提供getState()方法来获取state;</li>
<li>提供dispatch()方法更新state</li>
<li>通过subscribe(listener)注册监听器</li>
<li>通过subscribe(listener)返回的函数的注释监听器</li>
>* Redux应用中只有一个单一的store。当需要拆分数据处理逻辑的时候,我们应该去使用reducer组合,而不是创建多个store。 #### redux里面,store实例拥有四个方法 * getState:获取状态 * dispatch:改变状态 * subscribe:用于订阅状态更改事件,每当store的状态改变后,订阅的函数就会被订阅 * replaceReducer:用于替换创建store的reducer,比如在页面跳转到页面B,仅仅只需要替换reducer就可以让B页面使用所需的状态,在按需加载很有用。

Redux的三大原则

1. 单一数据源

  整个应用的state都被存储在一棵object tree中,并且这个object tree只存在。唯一一个store中。这使得同构应用的开发变得非常容易。在开发中,把应用的state保存到本地,从而加快开发速度。

2. state是只读的

  • 唯一改变state的方法就是触发action,action是一个用于描述已经发生的事件的普通对象。
  • 确保了视图和网络请求都不能直接修改state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化的去处理,且严格按照一个接一个顺序去执行。

3. 使用纯函数来执行修改

  为了描述action如何改变state tree,我们需要去编写reducers。
  Reducer是纯函数(一个函数的返回结果只依赖它的参数,而且在执行的过程中没有副作用,即纯函数),接收先前的state和store,并且返回新的state。随着应用的变大,我们可以把它拆成多个小的reducers,分别独立的操作state tree的不同部分。

  • 使用combineReducers(r1, r2)来合并几个reducer。
  • 使用createStore(reducer) 来创建store

Redux、Flux比较

 1、相同点:

  • 两者都规定,将模型的更新逻辑全部的集中在一个特定的层(Flux是store,Redux里的reducer),都不允许程序直接数据,而是通过一种叫作“action”的普通对象来更改进行描述。

 2、不同点:

  • 不同于Flux,Redux并没有dispatcher的概念。因为redux依赖纯函数来代替事件处理器,纯函数构建简单,不需额外的实体来管理它们。
  • 另一个重要的区别,Redux设想你永远不会变动的你的数据。应该在reducer中返回一个新的对象来更新state,配合一些库使用(Immutable)。

Redux的数据流

  1. 调用store.dispatch(action)
  • Action就是一个描述“发生了什么的”普通对象,实际修改state的是Reducer。
  • 可以在任何地方去调用,包括组件中、XHR回调中、甚至定时器
  1. Redux store调用传入的reducer函数
  • Store会把state和action这两个参数传入到reducer中,进行处理,并且返回一个新的state。
  • reducer是一个纯函数,不能做有副作用的操作(操作对外部产生影响)如API调用、路由跳转。
  1. 根reducer应该把多个reducer输出合并成一个单一的state树
  • 原生的Redux提供combineReducer()辅助函数,来把根reducer拆分成多个函数,用于分别处理state树的一个分支。
  1. Redux store保存了根reducer返回的完整的state树
  • 所有订阅store.subscribe(listener)的监听器都被调用;监听器里面还可以调用store.getState()来获取当前的state。

Middleware

  • 默认的情况下,createStore()所创建的Redux store是没有middleware的,所以只支持同步的数据流。通过applyMiddleware()来增强createStore()开,来描述异步的action。
  • Middleware主要提供的是位于action被发起之后,到达reducer之前的扩展点。可以利用Redux middleware来进行记录日志,创建崩溃报告,调用异步的接口或路由等。
  • 主要原理:是修改dispatch的方法,例如记录日志,我们就需要重新返回一个dispatch,在dispatch方法执行前输出内容,执行后输出内容
// Monkeypatching的设置,在函数的内部返回一个新的dispatch
function logger(store) {
    // 这里的next必须要指向前一个的middleware返回的函数,
    let next = store.dispatch;
    // dispatch(action)
    return funciton dispatchAndLog(action) {
        console.log(‘dispatching’, action);  // action发起后
        let result = next(action)       // reducer执行前
        console.log('next state', store.getState())
        return result
   }
}
// 优化Monkeypatching,middleware以方法参数的形式去接收一个next()方法(获取最新的dispatch),而是不通过store去调用
const logger = store => next => action => {
    console.log(‘dispatching’, action);
    let result = next(action)
    console.log('next state', store.getState())
    return result
}
// react-thunk来搭建异步的action构造器,使得我们可以发起一个函数来代替action,这个函数接收dispath,getState为参数
const thunk = store => next => action => 
   typeof action === 'function' ? action(store.dispatch, store.getState) : next(acion);

// 在Redux内部中我们可以将实际的monkeypatching应用到store.dispatch中的辅助的方法,传入store参数,对应的middleware也要设置好如何去修改里面的store.dispatch
function applyMiddleware(store, middlewares) {
    middlewares = middlewares.slice();
    middlewares.reverse();
    
    let dispatch = store.dispatch;
    middlewares.forEach(middleware => {
        dispatch = middleware(store)(dispatch);
    })
    return Object.assign({}, store, { dispatch });  // 返回store的副本
}

// applyMiddleWare()告诉createStore()如何处理中间件

redux-thunk

  • react-thunk来搭建异步的action构造器,使得我们可以发起一个函数来代替action,这个函数接收dispath,getState为参数。
  • 默认情况下,redux只能dispatch一个plain object。dispatch({type: 'xxx', data: 'xxx'})
  • 使用redux-thunk后,我们就可以dispatch一个函数,这个函数接收dispatch,getState作为参数。在这个函数里面干你想干的事情,在任意地方随意的dispatch,发起异步的操作。
// 源码 next = store.dispatch 
function createThunkMiddleware(extraArgument) {
    return ({dispatch, getState}) => next => action => {  // 返回一个新的dispatch
        if (typeof action === 'function'){
            return action(dispatch, getState, extraArgument); // 可以dispatch一个函数 
        }
        return next(action);
    };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

Redux API

1. creareStore(reducer, [preloadedState], enhancer)

  • 创建一个Redux store来存放应用中所有的state,应用中有且只有一个store。
  • reducer: 接收两个参数,分别试当前的state树和要处理的action,返回新的state树。
  • state普通对象,永远不要去修改它!不要使用Object.assign(state, newData), 应该使用Object.assign({}, state, newData)。不会覆盖旧的state。return {...state, ...newData},也可以使用展开运算符。

2. Store

  • Store 就是用来维持应用所有的 state 树 的一个对象。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action。
  • getState()
    dispatch(action)
    subscribe(listener: Function)
    replaceReducer(nextReducer)
  • 注意subscribe(listener),添加一个变化的监听器,每当dispatch action的时候就会执行,state的树一部分可能已经发生变化了。

3. combineReducers(reducers)

  • 随着应用变得复杂,需要对reducer函数进行拆分,拆分后的每一个独立负责管理state的一部分。
  • 它的组偶哦那个就是:把一个由多个不同的reducer函数作为value的object,合并为一个最终的reducer函数。然后对这个reducer调用createStore。
  • state的对象的结构,由传入的多个reducer的key来决定的。

4. applyMiddleware(...middlewares)

  • 以上写了。

5. bindActionCreators(actionCreators, dispatch)

  • 把action creators转化为拥有同名的keys的对象,但使用dispatch把每个action creator包围起来,这样可以直接调用它们。
  • 对reducer进行封装,让它不在显示。
  • 唯一使用 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它。
  • 主要用处:一般情况下,我们可以通过Provider将Store通过connect属性向下传递,bindActionCreators的唯一用处就是传递action creator到子组件,并且改子组件并没有接收到父组件上传递的store和dispatch

6. compose(...functions)

  • 从右到左来组合多个函数。参数是需要合成的多个函数。
  • 当需要把多个store增强器依次执行的时候,需要用到这个。

React-Redux API

1. Provider

  • Provider store使得组件层次中的connect的方法能够获得Redux store。正常情况下,根组件应该嵌套在Provider,才能使用connect()方法。

2. connect(mapStateToProps, mapDispatchToProps)(component)

  • 连接React组件与Redux Store。连接的组件不会改变原来的组件类,返回一个新的已经与Redux store的组件的类型。所以在当前的组件props可以获得state、actions
  • mapStateToProps:定义该参数,就会监听Redux store的变化。任何时候只要Redux store发生改变,mapStateToProps函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并
  • mapDispatchToProps:如果传递的是一个对象,那么定义的在该对象的函数都被当作React action creator,对象所定义的方法名将作为属性名。每个方法返回一个新的函数,函数中的dispatch方法会将action creator的返回值作为参数执行。这些属性都会被合并到组件的props中。
// 只注入dispatch 并不监听store
export default connect()(TodoApp)

// 注入全部没有订阅的store action creators
export default connect(null, actionCreators)(TodoApp)

// 注入dispatch和全局的state (尽量不要这样做,每次action都会触发整个TodoApp重新渲染。)
export default connect(state => state)(TodoApp)

模块规范AMD、CMD、CommonJs、UMD、ES6 Module

模块规范AMD、CMD、CommonJ、UMD、ES6 Module

1. AMD和Require.js

AMD异步方式加载模块,适用于浏览器!模块的加载不影响它后面的语句的执行,所有的依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
用require.js实现AMD规范的模块化:require.config()来指定引用的路径,用define()来定义模块,用require()来加载模块。

/** 网页中引入require.js以及main.js **/
<script src="js/require.js" data-main="js/main"></script>

/** main.js 入口文件/主模块 **/
// 使用require.config()来指定各模块路径和引用名
require.config({
    baseUrl: "js/lib",
    paths: {
        "jquery": "jquery.min",  // 实际路径为js/lib/jquery.min.js
        "underscore": "underscore.min",
    }
});

// 定义模块 如果该模块不依赖其他的模块,直接定义在define函数中,不需要添加第一个参数数组。
// 如果这个模块还依赖其他的模块,我们就需要指明该模块的依赖性,
// 第二个参数的函数的参数就是对其它模块的引用。require(函数加载上面这个模块的时候,先加载jquery.js文件)
define(['jquery'], function($) {
    // methods 
    function myFunc(){};
    
    // expose public methods 暴露公共的方法
    return myFunc(){};
})

// 加载模块
require(["jquery", "underscore"], function($, _) {
    // code...
})

Require.js主要为了解决:

  • 实现js文件的异步的加载,避免网页失去响应。
  • 管理模块之间的依赖性,保持严格的加载顺序,便于代码的编写和维护。

2.CMD和Sea.js

  • require.js在申明依赖的模块时会对第一时间加载并执行模块内的代码。
// AMD写法
define(['a','b','c','d','e','f'], function(a,b,c,d,e,f) {
    // 等于在最前面声明并初始化了要用到所有模块
    if (false) {
        // 即时没用使用到某个模块,但b还是要提前执行
        b.foo();
    }
})
  • CMD是另外的一种js模块化的方案,与ADM类似。
  • AMD推崇依赖前置,提前执行,加载模块内的代码;
  • CMD是推崇依赖就近,延迟执行。此规范在推广sea.js中产生的
// CMD写法
define(function(require, exports, module) {
    var a = require('./a'); // 在需要的时候申明
    a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }
});

/** sea.js **/
// 定义模块 main.js
define(function(require, exports, module) {
    var $ = require('jquery.js');
    var add = function(a, b) {
        return a+b;
    }
    exports.add = add;
});
// 加载模块 
seajs.use(['main.js'], function(math) {
    var sum = math.add(1, 2);
})

3. UMD

UMD通用的模块定义,结合AMD、CommonJS和谐相处。支持老式的global变量,通用的模块,结合两者的使用。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = factory(require('jQuery'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery);
    }
})(this, function($) {
    // methods
    function myFunc();
    
    // export public method
    return myFunc;
})


4. CommonJS

CommonJS流行于Node.js。已经变成采用Browserifykai d开发前端的开发者的一种趋势。使用于node。
使用同步的方式加载模块,因为在服务端,模块文件都保存在本地的磁盘,所以读取非常快。但是在浏览器端,限于网络的原因,更合理的是采用异步的加载!

// filename: foo.js

// dependencies
var $ = require('jquery');

// methods
function myFunc(){};

// export public method (single)
module.exports = myFunc;

5. ES6 Module

es6在语言标准的层面上,实现了模块的功能,旨在成为浏览器和服务器通用的模块解决方案。其模块的主要的命令构成:export和import。export命令用于对外的接口,import命令用于输入其他模块提供的模块。

/** 定义模块 math.js **/
var name = 'jerrylee';
var add = (a ,b) => {
    return  a + b;
}
// export default name; // 默认输出
export { name, add };

/** 引用模块 **/
import { name, add } from './math.js'
...

Es6的模块不是对象,import命令会被JS引擎静态解析,在编译的时候引入模块的代码,而不在运行的时候加载,所以无法实现条件加载。静态分析?

ES6模块与CommonJS模块的差异

  1. CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。
  • CommonJS模块的输出的值是值的拷贝,也就是说,一旦输出一个值,模块的内部的变化就不会影响到这个值。
  • ES6模块运行机制则不同。JS引擎对脚本静态的分析的时候,遇到模块加载命令import,就会生成一个只读的引用。等到脚本真正的执行的时候,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
  1. CommonJS模块是运行时加载,ES6模块是编译时输出接口。
  • 运行时加载:CommonJS模块就是对象;即在输入的时候先加载整个模块,生成一个对象,然后在从这个对象上面读取方法。
  • 编译时加载:ES6模块不是对象,而是通过export命令显式指定输出的代码。import时采用静态命令的形式。即在import时候可以指定加载某个输出值,而不是记载整个模块。

CommonJS加载的是一个对象(module.exports属性),该对象只有在脚本运行完才会生成。ES6模块并不是对象,它对外接口只是一个静态定义,在代码的静态解析阶段就会生成。

Redux中的connect

connect 主要的作用是连接React组件与Redux store。

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)
第一个参数就是传入Redux的store,第二个参数就是将action作为props绑定到组件上。

主要由于是Provider组件:1、在原组件上包裹一层,使得原来的整个应用都成为Provider的子组件
2、接收Redux的store作为props,通过context对象传递给子孙组件上的connect。

connect主要是真正连接Redux和React,它是抱在我们容器组件的外一层,接收上面的Provider提供的store里面的state和dispatch

传给一个构造函数,返回一个对象,以属性的形式传递给我们的容器组件。

源码的分析:connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithContext),

然后将真正的Component作为参数传递给wrapWithConnect,这样就产生了一个经过包裹了Connect的组件。(将数据都绑定到props上,并且获取到我们想要的数据)

理解:总的来说,是获取到父组件中的store,获取到自己想要的state、dispatch。并且作为props 传递给当前的组件!

这样当前的组件就可以通过this.props.state.xxx 获取state的值,
当前的组件就通过this.props.actions.xxx 来获取action中的方法,里面可以触发dispatch的事件。

浏览器的渲染机制与底层结构

浏览器的结构

  1. 用户界面(User Interface):包括地址栏、前进/后退按钮、书签菜单等。除了浏览器的主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
  2. 浏览器引擎(Browser engine):在用户界面和呈现引擎之间传送指令。
  3. 渲染引擎(Rendering engine):负责显示请求的内容。如果请求是内容是HTML,它就负责解析HTML和CSS内容,并将解析后的内容显示在屏幕上。
  4. 网络(Networking):用户网络调用,比如HTTP请求。其接口与平台无关,并为所有的平台提供底层实现。
  5. 用户界面后端(UI Backend):用户绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面的方法。
  6. JavaScript解释器(JS Interpreter):用于解析和执行JS的代码
  7. 数据存储(Data Persistence):数据持久层。浏览器的需要在硬盘上保存各种数据,比如Cookie。HTML5定义了“网络数据层”,只是一个完整的浏览器的内数据库。

各大浏览的内核分类:

  • Trident内核:IE
  • Gecko内核:FireFox
  • Webkit内核:Chrome、Safari

关键渲染路径

概念:是指与当前用户操作有关的内容。例如用户刚刚打开一个页面,首屏就显示当前用户的操作有关的内容,具体就是浏览器收到HTML、CSS和JS等资源并对其进行处理从而渲染出Web页面。

  • 优化关键渲染路径

输入网址到页面的呈现

  • DNS查询
  • TCP连接
  • HTTP请求即响应
  • 服务器响应
  • 客户端渲染(浏览器渲染)

浏览器对内容的渲染流程

  1. 处理HTML标记并构建DOM树
  2. 处理CSS标记并构建CSSOM树
  3. 将DOM与CSSOM合并为一个渲染树render tree
  4. 根据渲染树来布局,以计算每个节点的几何信息
  5. 将各个节点绘制到屏幕上去

阻塞渲染

谈论资源的阻塞的时候,现代的浏览器总是并行加载资源。例如,当HTML解析器(HTML Parser)被脚本阻塞的时候,解析器虽然会停止构建DOM,但仍会识别脚本后面的资源,并进行预加载。

由于以两点:

  1. 默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。
  2. JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。

存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。另外:

  • 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
  • JavaScript 可以查询和修改 DOM 与 CSSOM。
  • CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。

所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:

  • CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
  • JavaScript 应尽量少影响 DOM 的构建。

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.