Giter Club home page Giter Club logo

react-blog's Introduction

React-Blog

学习React全家桶,计划写一个博客。

边学边写边思考。。。

TodoList

  • 静态页面
  • 整理目录结构
  • Github issus作文章数据源
  • react-markdown渲染md
  • 代码高亮
  • 摘要
  • 分类
  • 标签
  • 翻页功能
  • 首页卡片
  • Demo页面
  • 评论功能(跳转到Github Issues页面。。)
  • 回到顶部
  • Redux管理数据
  • react-router路由
  • 整理代码
  • 渲染优化,打包优化
  • 适配移动端
  • React Native App

Update Log

时间 事件
2017/09/20 1.实现文章内部目录 2.博客页移动端自动隐藏右侧
2017/09/18 利用antd实现翻页功能
2017/09/17 1.增加类别页 2.修改Demo页 3.整理CSS代码
2017/09/11 增加归档页
2017/09/09 增加标签列表
2017/09/08 每个类别文章数
2017/09/07 1.加入Redux作为Github Issues数据的管理 2.增加分类模块
2017/09/06 整理项目目录结构
2017/09/04 1.修改首页卡片数据源 2.修改文章摘要 3.使用scrollIntoView()使页面改变后回到顶部
2017/09/03 1.增加文章页面 2.使用marked渲染Markdown 3.使用highlight.js高亮代码
2017/09/02 使用Github Issues作为文章数据源
2017/08/31 1.静态页面初步完成 2.使用本地json模拟数据源
2017/08/29 React-router V4 作为项目路由
2017/08/27 First Blood

问题汇总

  • React-router的Link和Route需要嵌套在同一个Router底下?
  • 刷新页面出现Cannot GET /提示,路由未生效。
    • 由于刷新之后,会根据URL对服务器发送请求,而不是处理路由,导致出现Cannot GET /错误。
    • 通过修改<Router><HashRouter>
    • <HashRouter>借助URL上的哈希值(hash)来实现路由。可以在不需要全屏刷新的情况下,达到切换页面的目的。
  • 二级路由只改变URL,页面不跳转
  • react-markdown不支持表格的渲染
  • 路由跳转后页面不会自动回到顶部
    • 使用scrollIntoView(),但是不能跳到整个页面的顶部。
  • Tag List显示字体大小应该如何计算。(或采用词云来展示?)
  • Expected 'this' to be used by class method。建议使用static,但是用了static之后bind会出错。。暂时关闭这个rule。。
  • 现在很多模块和css是重复的,需要整理代码。。
  • 文章内部目录的实现待优化,暂时也无法实现点击跳转
  • 首页渲染优化的问题
  • 本来想的在文章底部增加评论框,直接调用Github API创建评论和获取评论列表,但是发现需要登录授权,暂缓

react-blog's People

Contributors

axuebin avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-blog's Issues

家乡

暑假回家的时候拍的一张~


21140981

静谧的小山村

这是在临安牵牛岗下山途中看到的一个地方,刚好是日出的时候,雾气未散,暖暖的阳光洒在山间,构成的画面让人感觉很舒服。


  • 相机: SONY ILCE-6000
  • 镜头: E16-50mm
  • 焦距: 50mm
  • 光圈: f11
  • 快门:1/250s
  • ISO : 100

21136322

ES6变量命名方式以及块级作用域

之前看《深入理解es6》的笔记。。。


var声明及变量提升机制

在ES6之前,在函数作用域中或者全局作用域中通过var关键字来声明变量,无论是在代码的哪个位置,这条声明语句都会提到最顶部来执行,这就是变量声明提升。

注意:只是声明提升,初始化并没有提升。

看一个例子:

function getStudent(name){
  if(name){
    var age=25;
  }else{
    console.log("name不存在");      
  }
  console.log(age); //undefined
}

如果按照预想的代码的执行顺序,当name有值时才会创建变量age,可是执行代码发现,即使不传入name,判断语句外的输出语句并没有报错,而是输出undefined

这就是变量声明提升。

块级声明

ES6前是没有块级作用域的,比如{}外可以访问内部的变量。

let声明

  • 声明变量
  • 作用域限制在当前代码块
  • 声明不会提升
  • 禁止重声明(同一作用域不行,可以覆盖外部同名变量)
function getStudent(name){
  if(name){
    let age=25;
    console.log(age); //25
  }else{
    console.log("name不存在");      
  }
  console.log(age); //age is not defined
}

和上文一样的代码,只是将age的命名关键字从var改成了let,在执行getStudent()getStudent("axuebin")时都会报错。

原因:

  • 在if语句内部执行之后,age变量将立即被销毁
  • 如果name为空,则永远都不会创建age变量

const声明

  • 声明常量
  • 必须初始化
  • 不可更改
  • 作用域限制在当前代码块
  • 声明不会提升
  • 禁止重声明(同一作用域不行,可以覆盖外部同名变量)

如果用const来声明对象,则对象中的值可以修改。

临时死区(Temporal Dead Zone)

JavaScript引擎在扫面代码发现声明变量时,遇到var则提升到作用域顶部,遇到letconst则放到TDZ中。当执行了变量声明语句后,TDZ中的变量才能正常访问。

循环中的块作用域绑定

我们经常使用for循环:

for(var i=0;i<10;i++){
  console.log(i); //0,1,2,3,4,5,6,7,8,9
}
console.log(i) //10

发现了什么?

在for循环执行后,我们仍然可以访问到变量i

So easy ~ 把var换成let就解决了~

for(let i=0;i<10;i++){
  console.log(i); //0,1,2,3,4,5,6,7,8,9
}
console.log(i) //i is not defined

还记得当初讲闭包时setTimeout循环各一秒输出i的那个例子吗~

曾经熟悉的你 ~

for(var i=0;i<10;i++){
  setTimeout(function(){
    console.log(i); //10,10,10.....
  },1000)
}

很显然,上面的代码输出了10次的10,setTimeout在执行了循环之后才执行,此时i已经是10了~

之前,我们这样做 ~

for(var i=0;i<10;i++){
  setTimeout((function(i){
    console.log(i); //0,1,2,3,4,5,6,7,8,9
  })(i),1000)
}

现在,我们这样做 ~ 来看看把var改成let会怎样~

for(let i=0;i<10;i++){
  setTimeout(function(){
    console.log(i); //0,1,2,3,4,5,6,7,8,9
  },1000)
}

nice~

全局块作用域绑定

在全局作用域下声明的时

  • var会覆盖window对象中的属性
  • letconst会屏蔽,而不是覆盖,用window.还能访问到

关于isFetching的类型

用Redux的时候设置了一个isFetching值,默认应该是boolean类型,可是浏览器报错,认为是function类型…

原生JS实现最简单的图片懒加载

试一下自己撸一个图片懒加载...


Demo地址:http://axuebin.com/lazyload

照片都是自己拍的哦~

懒加载

什么是懒加载

懒加载其实就是延迟加载,是一种对网页性能优化的方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免打开网页时加载过多资源。

什么时候用懒加载

当页面中需要一次性载入很多图片的时候,往往都是需要用懒加载的。

懒加载原理

我们都知道HTML中的<img>标签是代表文档中的一个图像。。说了个废话。。

<img>标签有一个属性是src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有src属性,就不会发送请求。

嗯?貌似这点可以利用一下?

我先不设置src,需要的时候再设置?

nice,就是这样。

我们先不给<img>设置src,把图片真正的URL放在另一个属性data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到src中。

实现

HTML结构

<div class="container">
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img1.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img2.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img3.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img4.png">
  </div>
  <div class="img-area">
    <img class="my-photo" alt="loading" data-src="./img/img5.png">
  </div>
</div>

仔细观察一下,<img>标签此时是没有src属性的,只有altdata-src属性。

alt 属性是一个必需的属性,它规定在图像无法显示时的替代文本。
data-* 全局属性:构成一类名称为自定义数据属性的属性,可以通过HTMLElement.dataset来访问。

如何判断元素是否在可视区域

方法一

网上看到好多这种方法,稍微记录一下。

  1. 通过document.documentElement.clientHeight获取屏幕可视窗口高度
  2. 通过element.offsetTop获取元素相对于文档顶部的距离
  3. 通过document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离

然后判断②-③<①是否成立,如果成立,元素就在可视区域内。

方法二 getBoundingClientRect

通过getBoundingClientRect()方法来获取元素的大小以及位置,MDN上是这样描述的:

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

这个方法返回一个名为ClientRectDOMRect对象,包含了toprightbottonleftwidthheight这些值。

MDN上有这样一张图:

可以看出返回的元素位置是相对于左上角而言的,而不是边距。

我们思考一下,什么情况下图片进入可视区域。

假设const bound = el.getBoundingClientRect();来表示图片到可视区域顶部距离;
并设 const clientHeight = window.innerHeight;来表示可视区域的高度。

随着滚动条的向下滚动,bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。

也就是说,在bound.top<=clientHeight时,图片是在可视区域内的。

我们这样判断:

function isInSight(el) {
  const bound = el.getBoundingClientRect();
  const clientHeight = window.innerHeight;
  //如果只考虑向下滚动加载
  //const clientWidth = window.innerWeight;
  return bound.top <= clientHeight + 100;
}

这里有个+100是为了提前加载。

加载图片

页面打开时需要对所有图片进行检查,是否在可视区域内,如果是就加载。

function checkImgs() {
  const imgs = document.querySelectorAll('.my-photo');
  Array.from(imgs).forEach(el => {
    if (isInSight(el)) {
      loadImg(el);
    }
  })
}

function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src;
    el.src = source;
  }
}

这里应该是有一个优化的地方,设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。

函数节流

在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。

所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。

基本步骤:

  1. 获取第一次触发事件的时间戳
  2. 获取第二次触发事件的时间戳
  3. 时间差如果大于某个阈值就执行事件,然后重置第一个时间
function throttle(fn, mustRun = 500) {
  const timer = null;
  let previous = null;
  return function() {
    const now = new Date();
    const context = this;
    const args = arguments;
    if (!previous){
      previous = now;
    }
    const remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    }
  }
}

这里的mustRun就是调用函数的时间间隔,无论多么频繁的调用fn,只有remaining>=mustRunfn才能被执行。

实验

页面打开时

可以看出此时仅仅是加载了img1和img2,其它的img都没发送请求,看看此时的浏览器

第一张图片是完整的呈现了,第二张图片刚进入可视区域,后面的就看不到了~

页面滚动时

当我向下滚动,此时浏览器是这样

此时第二张图片完全显示了,而第三张图片显示了一点点,这时候我们看看请求情况

img3的请求发出来,而后面的请求还是没发出~

全部载入时

当滚动条滚到最底下时,全部请求都应该是发出的,如图

完整demo

在这哦:http://axuebin.com/lazyload

更新

方法三 IntersectionObserver

经大佬提醒,发现了这个方法

先附上链接:

jjc大大:justjavac/the-front-end-knowledge-you-may-not-know#10

阮一峰大大:http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

API Sketch for Intersection Observers:https://github.com/WICG/IntersectionObserver

IntersectionObserver可以自动观察元素是否在视口内。

var io = new IntersectionObserver(callback, option);
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();

callback的参数是一个数组,每个数组都是一个IntersectionObserverEntry对象,包括以下属性:

属性 描述
time 可见性发生变化的时间,单位为毫秒
rootBounds 与getBoundingClientRect()方法的返回值一样
boundingClientRect 目标元素的矩形区域的信息
intersectionRect 目标元素与视口(或根元素)的交叉区域的信息
intersectionRatio 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
target 被观察的目标元素,是一个 DOM 节点对象

我们需要用到intersectionRatio来判断是否在可视区域内,当intersectionRatio > 0 && intersectionRatio <= 1即在可视区域内。

代码

function checkImgs() {
  const imgs = Array.from(document.querySelectorAll(".my-photo"));
  imgs.forEach(item => io.observe(item));
}

function loadImg(el) {
  if (!el.src) {
    const source = el.dataset.src;
    el.src = source;
  }
}

const io = new IntersectionObserver(ioes => {
  ioes.forEach(ioe => {
    const el = ioe.target;
    const intersectionRatio = ioe.intersectionRatio;
    if (intersectionRatio > 0 && intersectionRatio <= 1) {
      loadImg(el);
    }
    el.onload = el.onerror = () => io.unobserve(el);
  });
});

我是向您学习的

看看别人留下的issues会不会留在你的博客里,我也想学着去弄一个这样的博客,主要在博客发布方面遭遇了问题

文章右侧目录的写法

  1. 正则获取一个没有层级的顺序数组
  2. 构建带有层级关系的对象数组
  3. 构建DOM元素,也就是ul、li列表

觉得现在这种方式,特别是我的写法很丑,有待改进。

React中state和props分别是什么?

整理一下React中关于state和props的知识点。


在任何应用中,数据都是必不可少的。我们需要直接的改变页面上一块的区域来使得视图的刷新,或者间接地改变其他地方的数据。React的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在propsstate中,这两个属性有啥子区别呢?

props

React的核心**就是组件化**,页面会被切分成一些独立的、可复用的组件。

组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据。由于React是单向数据流,所以props基本上也就是从服父级组件向子组件传递的数据。

用法

假设我们现在需要实现一个列表,根据React组件化**,我们可以把列表中的行当做一个组件,也就是有这样两个组件:<ItemList/><Item/>

先看看<ItemList/>

import Item from "./item";
export default class ItemList extends React.Component{
  const itemList = data.map(item => <Item item=item />);
  render(){
    return (
      {itemList}
    )
  }
}

列表的数据我们就暂时先假设是放在一个data变量中,然后通过map函数返回一个每一项都是<Item item='数据'/>的数组,也就是说这里其实包含了data.length<Item/>组件,数据通过在组件上自定义一个参数传递。当然,这里想传递几个自定义参数都可以。

<Item />中是这样的:

export default class Item extends React.Component{
  render(){
    return (
      <li>{this.props.item}</li>
    )
  }
}

render函数中可以看出,组件内部是使用this.props来获取传递到该组件的所有数据,它是一个对象,包含了所有你对这个组件的配置,现在只包含了一个item属性,所以通过this.props.item来获取即可。

只读性

props经常被用作渲染组件和初始化状态,当一个组件被实例化之后,它的props是只读的,不可改变的。如果props在渲染过程中可以被改变,会导致这个组件显示的形态变得不可预测。只有通过父组件重新渲染的方式才可以把新的props传入组件中。

默认参数

在组件中,我们最好为props中的参数设置一个defaultProps,并且制定它的类型。比如,这样:

Item.defaultProps = {
  item: 'Hello Props',
};

Item.propTypes = {
  item: PropTypes.string,
};

关于propTypes,可以声明为以下几种类型:

optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

注意,boolfunc是简写。

这些知识基础数据类型,还有一些复杂的,附上链接:

https://facebook.github.io/react/docs/typechecking-with-proptypes.html

总结

props是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。

state

state是什么呢?

State is similar to props, but it is private and fully controlled by the component.

一个组件的显示形态可以由数据状态和外部参数所决定,外部参数也就是props,而数据状态就是state

用法

export default class ItemList extends React.Component{
  constructor(){
    super();
    this.state = {
      itemList:'一些数据',
    }
  }
  render(){
    return (
      {this.state.itemList}
    )
  }
}

首先,在组件初始化的时候,通过this.state给组件设定一个初始的state,在第一次render的时候就会用这个数据来渲染组件。

setState

state不同于props的一点是,state是可以被改变的。不过,不可以直接通过this.state=的方式来修改,而需要通过this.setState()方法来修改state

比如,我们经常会通过异步操作来获取数据,我们需要在didMount阶段来执行异步操作:

componentDidMount(){
  fetch('url')
    .then(response => response.json())
    .then((data) => {
      this.setState({itemList:item});  
    }
}

当数据获取完成后,通过this.setState来修改数据状态。

当我们调用this.setState方法时,React会更新组件的数据状态state,并且重新调用render方法,也就是会对组件进行重新渲染。

注意:通过this.state=来初始化state,使用this.setState来修改stateconstructor是唯一能够初始化的地方。

setState接受一个对象或者函数作为第一个参数,只需要传入需要更新的部分即可,不需要传入整个对象,比如:

export default class ItemList extends React.Component{
  constructor(){
    super();
    this.state = {
      name:'axuebin',
      age:25,
    }
  }
  componentDidMount(){
    this.setState({age:18})  
  }
}

在执行完setState之后的state应该是{name:'axuebin',age:18}

setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成:

this.setState({
  name:'xb'
},()=>console.log('setState finished'))

总结

state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

区别

  1. state是组件自己管理数据,控制自己的状态,可变;
  2. props是外部传入的数据参数,不可变;
  3. 没有state的叫做无状态组件,有state的叫做有状态组件;
  4. 多用props,少用state。也就是多写无状态组件。

JavaScript数据类型的存储

一个很基础的知识点,JavaScript中基本数据类型和引用数据类型是如何存储的。


由于自己是野生程序员,在刚开始学习程序设计的时候没有在意内存这些基础知识,导致后来在提到“什么什么是存在栈中的,栈中只是存了一个引用”这样的话时总是一脸懵逼。。

后来渐渐的了解了一些内存的知识,这部分还是非常有必要了解的。

基本数据结构

栈,只允许在一段进行插入或者删除操作的线性表,是一种先进后出的数据结构。

堆是基于散列算法的数据结构。

队列

队列是一种先进先出(FIFO)的数据结构。

JavaScript中数据类型的存储

JavaScript中将数据类型分为基本数据类型和引用数据类型,它们其中有一个区别就是存储的位置不同。

基本数据类型

我们都知道JavaScript中的基本数据类型有:

  • String
  • Number
  • Boolean
  • Undefined
  • Null
  • Symbol(暂时不管)

基本数据类型都是一些简单的数据段,它们是存储在栈内存中。

引用数据类型

JavaScript中的引用数据类型有:

  • Array
  • Object

引用数据类型是保存在堆内存中的,然后再栈内存中保存一个对堆内存中实际对象的引用。所以,JavaScript中对引用数据类型的操作都是操作对象的引用而不是实际的对象。

可以理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的。

图解

现在,我们声明几个变量试试:

var name="axuebin";
var age=25;
var job;
var arr=[1,2,3];
var obj={age:25};

可以通过下图来表示数据类型在内存中的存储情况:

此时name,age,job三种基本数据类型是直接存在栈内存中的,而arr,obj在栈内存中只是存了一个地址来表示对堆内存中的引用。

复制

基本数据类型

对于基本数据类型,如果进行复制,系统会自动为新的变量在栈内存中分配一个新值,很容易理解。

引用数据类型

如果对于数组、对象这样的引用数据类型而言,复制的时候就会有所区别了:

系统也会自动为新的变量在栈内存中分配一个值,但这个值仅仅是一个地址。也就是说,复制出来的变量和原有的变量具有相同的地址值,指向堆内存中的同一个对象。

如果所示,执行了var objCopy=obj之后,objobjCopy具有相同的地址值,执行堆内存中的同一个实际对象。

这有什么不同呢?

当我修改objobjCopy时,都会引起另一个变量的改变。

为什么?

为什么基础数据类型存在栈中,而引用数据类型存在堆中呢?

  1. 堆比栈大,栈比对速度快。
  2. 基础数据类型比较稳定,而且相对来说占用的内存小。
  3. 引用数据类型大小是动态的,而且是无限的。
  4. 堆内存是无序存储,可以根据引用直接获取。

参考文章

http://www.jianshu.com/p/996671d4dcc4
http://blog.sina.com.cn/s/blog_8ecde0fe0102vy6e.html

React V15.6 实现一个简单的个人博客

学习 React 的过程中实现了一个个人主页,没有复杂的实现和操作,适合入门 ~


这个项目其实功能很简单,就是常见的主页、博客、demo、关于我等功能。

页面样式都是自己写的,黑白风格,可能有点丑。不过还是最低级的 CSS ,准备到时候重构 ~

如果有更好的方法,或者是我的想法有偏差的,欢迎大家交流指正

欢迎参观:http://axuebin.com/react-blog

Github:https://github.com/axuebin/react-blog

预览图

首页

博客页

文章内容页

Demo页

关键技术

  • ES6:项目中用到 ES6 的语法,在写的过程中尽量使用,可能有的地方没想到
  • React
  • React-Router:前端路由
  • React-Redux:状态管理
  • webpack:打包
  • marked:Markdown渲染
  • highlight.js:代码高亮
  • fetch:异步请求数据
  • eslint:代码检查
  • antd:部分组件懒得自己写。。

准备工作

由于不是使用 React 脚手架生成的项目,所以每个东西都是自己手动配置的。。。

模块打包器

打包用的是 webpack 2.6.1,准备入坑 webpack 3

官方文档:https://webpack.js.org/

中文文档:https://doc.webpack-china.org/

对于 webpack 的配置还不是太熟,就简单的配置了一下可供项目启动:

var webpack = require('webpack');
var path = require('path');

module.exports = {
  context: __dirname + '/src',
  entry: "./js/index.js",
  module: {
    loaders: [
      {
        test: /\.js?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015']
        }
      }, {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }, {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'eslint-loader'
      }, {
        test: /\.json$/,
        loader: 'json-loader'
      }
    ]
  },
  output: {
    path: __dirname + "/src/",
    filename: "bundle.js"
  }
}

webpack 有几个重要的属性:entrymoduleoutputplugins,在这里我还没使用到插件,所以没有配置 plugins

module 中的 loaders

  • babel-loader:将代码转换成es5代码
  • css-loader:处理css中路径引用等问题
  • style-loader:动态把样式写入css
  • eslin-loader:使用eslint

包管理

包管理现在使用的还是 NPM

官方文档:https://docs.npmjs.com/

  1. npm init
  2. npm install
  3. npm uninstall

关于npm,可能还需要了解 dependenciesdevDependencies 的区别,我是这样简单理解的:

  • dependencies:项目跑起来后需要使用到的模块
  • devDependencies:开发的时候需要用的模块,但是项目跑起来后就不需要了

代码检查

项目使用现在比较流行的 ESLint 作为代码检查工具,并使用 Airbnb 的检查规则。

ESLint:https://github.com/eslint/eslint

eslint-config-airbnb:https://www.npmjs.com/package/eslint-config-airbnb

package.json 中可以看到,关于 ESLint 的包就是放在 devDependencies 底下的,因为它只是在开发的时候会使用到。

使用

  • webpack 配置中加载 eslint-loader
module: {
  loaders: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'eslint-loader'
      }
    ]
  }
  • 创建 .elintrc文件:
{
  "extends": "airbnb",
  "env":{
    "browser": true
  },
  "rules":{}
}

然后在运行 webpack 的时候,就会执行代码检查啦,看着一堆的 warningerror 是不是很爽~

这里有常见的ESLint规则:http://eslint.cn/docs/rules/

数据源

由于是为了练习 React,暂时就只考虑搭建一个静态页面,而且现在越来越多的大牛喜欢用 Github Issues 来写博客,也可以更好的地提供评论功能,所以我也想试试用 Github Issues 来作为博客的数据源。

API在这:https://developer.github.com/v3/issues/

我也没看完全部的API,就看了看怎么获取 Issues 列表。。

https://api.github.com/repos/axuebin/react-blog/issues?creator=axuebin&labels=blog

通过控制参数 creatorlabels,可以筛选出作为展示的 Issues。它会返回一个带有 issue 格式对象的数组。每一个 issue 有很多属性,我们可能不需要那么多,先了解了解底下这几种:

// 为了方便,我把注释写在json中了。。
[{
  "url": ,  // issue 的 url
  "id": ,  // issue id , 是一个随机生成的不重复的数字串 
  "number": ,  // issue number , 根据创建 issue 的顺序从1开始累加
  "title": ,  // issue 的标题
  "labels": [], // issue 的所有 label,它是一个数组
  "created_at": , // 创建 issue 的时间
  "updated_at": , // 最后修改 issue 的时间
  "body": , // issue 的内容
}]

异步请求数据

项目中使用的异步请求数据的方法时 fetch

关于 fetchhttps://segmentfault.com/a/1190000003810652

使用起来很简单:

fetch(url).then(response => response.json())
      .then(json => console.log(json))
      .catch(e => console.log(e));

markdown 渲染

Github 上查找关于如何在 React 实现 markdown 的渲染,查到了这两种库:

使用起来都很简单。

如果是 react-markdown,只需要这样做:

import ReactMarkdown from 'react-markdown';

const input = '# This is a header\n\nAnd this is a paragraph';
ReactDOM.render(
    <ReactMarkdown source={input} />,
    document.getElementById('container')
);

如果是marked,这样做:

import marked from 'marked';

const input = '# This is a header\n\nAnd this is a paragraph';
const output = marked(input);

这里有点不太一样,我们获取到了一个字符串 output,注意,是一个字符串,所以我们得将它插入到 dom中,在 React 中,我们可以这样做:

<div dangerouslySetInnerHTML={{ __html: output }} />

由于我们的项目是基于 React 的,所以想着用 react-markdown会更好,而且由于安全问题 React 也不提倡直接往 dom 里插入字符串,然而在使用过程中发现,react-markdown 对表格的支持不友好,所以只好弃用,改用 marked

代码高亮

代码高亮用的是highlight.jshttps://github.com/isagalaev/highlight.js

它和marked可以无缝衔接~

只需要这样既可:

import hljs from 'highlight.js';

marked.setOptions({
  highlight: code => hljs.highlightAuto(code).value,
});

highlight.js是支持多种代码配色风格的,可以在css文件中进行切换:

@import '~highlight.js/styles/atom-one-dark.css';

在这可以看到每种语言的高亮效果和配色风格:https://highlightjs.org/

React

state 和 props 是什么

可以看之前的一篇文章:#8

关于React组件的生命周期

可以看之前的一篇文章:#9

前端路由

项目中前端路由用的是 React-Router V4

官方文档:https://reacttraining.com/react-router/web/guides/quick-start

中文文档:http://reacttraining.cn/

基本使用

<Link to="/blog">Blog</Link>
<Router>
  <Route exact path="/" component={Home} />
  <Route path="/blog" component={Blog} />
  <Route path="/demo" component={Demo} />
</Router>

注意:一定要在根目录的 Route 中声明 exact,要不然点击任何链接都无法跳转。

2级目录跳转

比如我现在要在博客页面上点击跳转,此时的 urllocalhost:8080/blog,需要变成 localhost:8080/blog/article,可以这样做:

<Route path={`${this.props.match.url}/article/:number`} component={Article} />

这样就可以跳转到 localhost:8080/blog/article 了,而且还传递了一个 number 参数,在 article 中可以通过 this.props.params.number获取。

HashRouter

当我把项目托管到 Github Page 后,出现了这样一个问题。

刷新页面出现 Cannot GET / 提示,路由未生效。

通过了解,知道了原因是这样,并且可以解决:

  • 由于刷新之后,会根据URL对服务器发送请求,而不是处理路由,导致出现 Cannot GET / 错误。
  • 通过修改 <Router><HashRouter>
  • <HashRouter> 借助URL上的哈希值(hash)来实现路由。可以在不需要全屏刷新的情况下,达到切换页面的目的。

路由跳转后不会自动回到顶部

当前一个页面滚动到一定区域后,点击跳转后,页面虽然跳转了,但是会停留在滚动的区域,不会自动回到页面顶部。

可以通过这样来解决:

componentDidMount() {
    this.node.scrollIntoView();
}

render() {
  return (
    <div ref={node => this.node = node} ></div>
  );
}

状态管理

项目中多次需要用到从 Github Issues 请求来的数据,因为之前就知道 Redux 这个东西的存在,虽然有点大材小用,为了学习还是将它用于项目的状态管理,只需要请求一次数据即可。

官方文档:http://redux.js.org/

中文文档:http://cn.redux.js.org/

简单的来说,每一次的修改状态都需要触发 action ,然而其实项目中我现在还没用到修改数据2333。。。

关于状态管理这一块,由于还不是太了解,就不误人子弟了~

主要组件

React是基于组件构建的,所以在搭建页面的开始,我们要先考虑一下我们需要一些什么样的组件,这些组件之间有什么关系,哪些组件是可以复用的等等等。

首页

可以看到,我主要将首页分成了四个部分:

  • header:网站标题,副标题,导航栏
  • banner:about me ~,准备用自己的照片换个背景,但是还没有合适的照片
  • card area:暂时是三个卡片
    • blog card:最近的几篇博文
    • demo card:几个小demo类别
    • me card:算是我放飞自我的地方吧
  • footer:版权信息、备案信息、浏览量

博客页

博客页就是很中规中矩的一个页面吧,这部分是整个项目中代码量最多的部分,包括以下几部分:

  • 文章列表组件
  • 翻页组件
  • 归档按钮组件
  • 类别组件
  • 标签组件

文章列表

文章列表其实就是一个 list,里面有一个个的 item:

<div class="archive-list">
  <div class="blog-article-item">文章1</div>
  <div class="blog-article-item">文章2</div>
<div>

对于每一个 item,其实是这样的:

一个文章item组件它可能需要包括:

  • 文章标题
  • 文章发布的时间、类别、标签等
  • 文章摘要
  • ...

如果用 DOM 来描述,它应该是这样的:

<div class="blog-article-item">
  <div class="blog-article-item-title">文章标题</div>
  <div class="blog-article-item-time">时间</div>
  <div class="blog-article-item-label">类别</div>
  <div class="blog-article-item-label">标签</div>
  <div class="blog-article-item-desc">摘要</div>
</div>

所以,我们可以有很多个组件:

  • 文章列表组件 <ArticleList />
  • 文章item组件 <ArticleItem />
  • 类别标签组件 <ArticleLabel />

它们可能是这样一个关系:

<ArticleList>
  <ArticleItem>
    <ArticleTitle />
    <ArticleTime />
    <ArticleLabel />
    <ArticleDesc />
  </ArticleItem>
  <ArticleItem></ArticleItem>
  <ArticleItem></ArticleItem>
</ArticleList>

分页

对于分页功能,传统的实现方法是在后端完成分页然后分批返回到前端的,比如可能会返回一段这样的数据:

{
  total:500,
  page:1,
  data:[]
}

也就是后端会返回分好页的数据,含有表示总数据量的total、当前页数的page,以及属于该页的数据data

然而,我这个页面只是个静态页面,数据是放在Github Issues上的通过API获取的。(Github Issues的分页貌似不能自定义数量...),所以没法直接返回分好的数据,所以只能在前端强行分页~

分页功能这一块我偷懒了...用的是 antd 的翻页组件 <Pagination />

官方文档:https://ant.design/components/pagination-cn/

文档很清晰,使用起来也特别简单。

前端渲染的逻辑(有点蠢):将数据存放到一个数组中,根据当前页数和每页显示条数来计算该显示的索引值,取出相应的数据即可。

翻页组件中:

constructor() {
  super();
  this.onChangePage = this.onChangePage.bind(this);
}

onChangePage(pageNumber) {
  this.props.handlePageChange(pageNumber);
}

render() {
  return (
    <div className="blog-article-paging">
      <Pagination onChange={this.onChangePage} defaultPageSize={this.props.defaultPageSize} total={this.props.total} />
    </div>
  );
}

当页数发生改变后,会触发从父组件传进 <ArticlePaging /> 的方法 handlePageChange,从而将页数传递到父组件中,然后传递到 <ArticleList /> 中。

父组件中:

handlePageChange(pageNumber) {
  this.setState({ currentPage: pageNumber });
}

render() {
  return (
    <div className="archive-list-area">
      <ArticleList issues={this.props.issues} defaultPageSize={this.state.defaultPageSize} pageNumber={this.state.currentPage} />
      <ArticlePaging handlePageChange={this.handlePageChange} total={this.props.issues.length} defaultPageSize={this.state.defaultPageSize} />
    </div>
  );
}

列表中:

render() {
  const articlelist = [];
  const issues = this.props.issues;
  const currentPage = this.props.pageNumber;
  const defaultPageSize = this.props.defaultPageSize;
  const start = currentPage === 1 ? 0 : (currentPage - 1) * defaultPageSize;
  const end = start + defaultPageSize < issues.length ? start + defaultPageSize : issues.length;
  for (let i = start; i < end; i += 1) {
    const item = issues[i];
    articlelist.push(<ArticleItem />);
  }
}

label

Github Issues 中,可以为一个 issue 添加很多个 label,我将这些对于博客内容有用的 label 分为三类,分别用不同颜色来表示。

这里说明一下, label 创建后会随机生成一个 id,虽然说 id 是不重复的,但是文章的类别、标签会一直在增加,当新加一个 label 时,程序中可能也要进行对应的修改,当作区分 label 的标准可能就不太合适,所以我采用颜色来区分它们。

  • 表示这是一篇文章的blog:只有有 blogissue 才能显示在页面上,过滤 bughelp
  • 表示文章类别的:用来表示文章的类别,比如“前端”、“摄影”等
  • 表示文章标签的:用来表示文章的标签,比如“JavaScript”、“React”等

即使有新的 label ,也只要根据颜色区分是属于哪一类就好了。

类别

在这里的思路主要就是:遍历所有 issues,然后再遍历每个 issuelabels,找出属于类别的 label,然后计数。

const categoryList = [];
const categoryHash = {};
for (let i = 0; i < issues.length; i += 1) {
  const labels = issues[i].labels;
  for (let j = 0; j < labels.length; j += 1) {
    if (labels[j].color === COLOR_LABEL_CATEGORY) {
      const category = labels[j].name;
      if (categoryHash[category] === undefined) {
        categoryHash[category] = true;
        const categoryTemp = { category, sum: 1 };
        categoryList.push(categoryTemp);
      } else {
        for (let k = 0; k < categoryList.length; k += 1) {
          if (categoryList[k].category === category) {
            categoryList[k].sum += 1;
          }
        }
      }
    }
  }
}

这样实现得要经历三次循环,复杂度有点高,感觉有点蠢,有待改进,如果有更好的方法,请多多指教~

标签

这里的思路和类别的思路基本一样,只不过不同的显示方式而已。

本来这里是想通过字体大小来体现每个标签的权重,后来觉得可能对于我来说,暂时只有那几个标签会很频繁,其它标签可能会很少,用字体大小来区分就没有什么意义,还是改成排序的方式。

文章页

文章页主要分为两部分:

  • 文章内容区域:显示文章内容,显示在页面的主体区域
  • 章节目录:文章的章节目录,显示在文章的右侧区域

文章内容

有两种方式获取文章具体内容:

  • 从之前已经请求过的数组中去遍历查找所需的文章内容
  • 通过 issue number 重新发一次请求直接获取内容

最后我选择了后者。

文章是用 markdown 语法写的,所以要先转成 html 然后插入页面中,这里用了一个 React 不提倡的属性:dangerouslySetInnerHTML

除了渲染markdown,我们还得对文章中的代码进行高亮显示,还有就是定制文章中不同标签的样式。

章节目录

首先,这里有一个 issue,希望大家可以给一些建议~

文章内容是通过 markdown 渲染后插入 dom 中的,由于 React 不建议通过 document.getElementById 的形式获取 dom 元素,所以只能想办法通过字符串匹配的方式获取文章的各个章节标题。

由于我不太熟悉正则表达式,曾经还在sf上咨询过,就采用了其中一个答案:

const issues = content;
const menu = [];
const patt = /(#+)\s+?(.+)/g;
let result = null;
while ((result = patt.exec(issues))) {
  menu.push({ level: result[1].length, title: result[2] });
}

这样可以获取到所有的 # 的字符串,也就是 markdown 中的标题, result[1].length 表示有几个 #,其实就是几级标题的意思,title 就是标题内容了。

这里还有一个问题,本来通过 <a target="" /> 的方式可以实现点击跳转,但是现在渲染出来的 html 中对于每一个标题没有独一无二的标识。。。

归档页

按年份归档:

按类别归档:

按标签归档:

问题

基本功能是已经基本实现了,现在还存在着以下几个问题,也算是一个 TodoList

  • 评论功能。拟利用 Github Issues API 实现评论,得实现 Github 授权登录
  • 回到顶部。拟利用 antd 的组件,但是 statevisibility 一直是 false
  • 首页渲染。现在打包完的js文件还是太大了,导致首页渲染太慢,这个是接下来工作的重点,也了解过关于这方面的优化:
    • webpack 按需加载。这可能是目前最方便的方式
    • 服务端渲染。这就麻烦了,但是好处也多,不仅解决渲染问题,还有利于SEO,所以也是 todo 之一
  • 响应式。现在的样式都是在PC端的,还未适配移动端。
  • 代码混乱,逻辑不对。这是我自己的问题,需要再修炼。

常见水平居中和垂直居中方法

总结一下常见的水平居中和垂直居中的方法。


水平居中

行内元素的居中

对于行内元素,居中简直不要太容易,父级元素中设置一个text-align:center即可。

定宽块级元素的居中1

对于块级元素,如果知道它的宽度,居中起来也会很容易,比如这样:

<div class="horizontal">
  <div class="demo demo-2">
    <div class="demo-2-item">我是块级元素</div>
  </div>
</div>
.demo-2-item{
  width:200px;
  height:30px;
  background-color: lightblue;
  margin: 0 auto;
}

也就是设置了宽度之后,另它的margin-leftmargin-rightauto

定宽块级元素的居中2

定宽块级元素还能这样:

<div class="horizontal">
  <div class="demo demo-5">
    <div class="demo-5-item">我是要居中的那个</div>
  </div>
</div>
.demo-5-item{
  position:absolute;
  width:200px;
  left:50%;
  margin-left:-100px;
  background-color: lightblue;
}

将它设为绝对定位,然后用left:50%将元素的最左侧移到绝对定位的正中间,再利用margin-left:-100px将它向左移动100px,也就是宽度的一半,就正好将元素的中线和容器的中线对齐了,实现居中效果。

定宽块级元素的居中3

还有一种利用绝对定位的方式:

<div class="horizontal">
  <div class="demo demo-6">
    <div class="demo-6-item">我是要居中的那个</div>
  </div>
</div>
.demo-5-item{
  position:absolute;
  width:200px;
  left:0;
  right:0;
  margin:0 auto;
  background-color: lightblue;
}

left:0;right:0的作用

我们先看leftright两个属性,在绝对定位中,如果设置了top: 0; left: 0; bottom: 0; right: 0 ,浏览器为它包裹一层新的盒子,这个元素会填满它相对父元素的内部空间。

假如我只设置了left:0;right:0;也就是这样:

蓝色区域会充满整个父容器的宽度。

width:200px的作用

如果给元素设置了宽度,浏览器会阻止元素填满所有的空间。也就是这样:

margin:0 auto的作用

根据盒子的计算规则,如果宽度是固定的,将margin-leftmargin-right设为auto之后,这两个值会根绝父级元素的宽度自动计算除去元素的剩余宽度,然后均分,就实现了居中效果了。

flex

通过flex布局可以轻易实现居中,父级元素设置display: flex;justify-content: center;即可。

CSS3:transform

使用CSS3中新增的transform属性, 子元素设置:

position:absolute;
left:50%;
transform:translate(-50%,0);

grid

使用Grid来实现,父级元素设置:

display: grid;
grid-template-columns: 50px;
justify-content: center;

垂直居中

未完待续

图虫

类别测试 。。。附上我的图虫链接~


图虫

小白学《深入理解es6》--块级作用域绑定

之前看《深入理解es6》记的笔记。。


var声明及变量提升机制

在ES6之前,在函数作用域中或者全局作用域中通过var关键字来声明变量,无论是在代码的哪个位置,这条声明语句都会提到最顶部来执行,这就是变量声明提升。

注意:只是声明提升,初始化并没有提升。

看一个例子:

function getStudent(name){
  if(name){
    var age=25;
  }else{
    console.log("name不存在");      
  }
  console.log(age); //undefined
}

如果按照预想的代码的执行顺序,当name有值时才会创建变量age,可是执行代码发现,即使不传入name,判断语句外的输出语句并没有报错,而是输出undefined

这就是变量声明提升。

块级声明

ES6前是没有块级作用域的,比如{}外可以访问内部的变量。

let声明

  • 声明变量
  • 作用域限制在当前代码块
  • 声明不会提升
  • 禁止重声明(同一作用域不行,可以覆盖外部同名变量)
function getStudent(name){
  if(name){
    let age=25;
    console.log(age); //25
  }else{
    console.log("name不存在");      
  }
  console.log(age); //age is not defined
}

和上文一样的代码,只是将age的命名关键字从var改成了let,在执行getStudent()getStudent("axuebin")时都会报错。

原因:

  • 在if语句内部执行之后,age变量将立即被销毁
  • 如果name为空,则永远都不会创建age变量

const声明

  • 声明常量
  • 必须初始化
  • 不可更改
  • 作用域限制在当前代码块
  • 声明不会提升
  • 禁止重声明(同一作用域不行,可以覆盖外部同名变量)

如果用const来声明对象,则对象中的值可以修改。

临时死区(Temporal Dead Zone)

JavaScript引擎在扫面代码发现声明变量时,遇到var则提升到作用域顶部,遇到letconst则放到TDZ中。当执行了变量声明语句后,TDZ中的变量才能正常访问。

循环中的块作用域绑定

我们经常使用for循环:

for(var i=0;i<10;i++){
  console.log(i); //0,1,2,3,4,5,6,7,8,9
}
console.log(i) //10

发现了什么?

在for循环执行后,我们仍然可以访问到变量i

So easy ~ 把var换成let就解决了~

for(let i=0;i<10;i++){
  console.log(i); //0,1,2,3,4,5,6,7,8,9
}
console.log(i) //i is not defined

还记得当初讲闭包时setTimeout循环各一秒输出i的那个例子吗~

曾经熟悉的你 ~

for(var i=0;i<10;i++){
  setTimeout(function(){
    console.log(i); //10,10,10.....
  },1000)
}

很显然,上面的代码输出了10次的10,setTimeout在执行了循环之后才执行,此时i已经是10了~

之前,我们这样做 ~

for(var i=0;i<10;i++){
  setTimeout((function(i){
    console.log(i); //0,1,2,3,4,5,6,7,8,9
  })(i),1000)
}

现在,我们这样做 ~ 来看看把var改成let会怎样~

for(let i=0;i<10;i++){
  setTimeout(function(){
    console.log(i); //0,1,2,3,4,5,6,7,8,9
  },1000)
}

nice~

全局块作用域绑定

在全局作用域下声明的时

  • var会覆盖window对象中的属性
  • letconst会屏蔽,而不是覆盖,用window.还能访问到

用React实现一个简易的TodoList

初学React,撸一个TodoList熟悉熟悉基本语法,只有最简单最简单的功能。


如上图所示,是一个最简单的TodoList的样子了,我们应该怎样把它拆成一个个的组件呢?

在之前看来,可能就是这样一个HTML结构:

<div>
  <h1></h1>
  <div>
    <ul>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </div>
  <div>
    <input/>
    <button>保存</button>
  </div>
</div>

React的核心**是:封装组件。

我们也可以按照这个思路来进行组件设计

组件设计

从小到大,从内到外 ~

我是这样进行设计的。

除去按钮,input这些之外,<li></li>是HTML中最小的元素,我们可以先每一个<li></li>当成是一个最小的组件,也就是图中橙色框的部分,它对应着每一条内容,我们先把它命名为TodoItem吧。

<li></li>的父级元素是<ul></ul>,那就把它看作一个组件呗,图中位于上方的蓝色部分,命名为TodoList

恩,此时Todo内容的展示组件已经是够的了,我们再来加一个添加Todo内容的组件AddTodoItem吧,命名貌似有点丑- -,图中位于下方的蓝色部分。

最后就是最外层的红色部分了,它就是整个app的主体部分,包含着其它小组件,命名为TodoBox

ok,暂时就这几个小组件 ~

然我们开始愉快的撸代码吧 ~

代码部分

Index

先看看入口程序,很简单。

var React = require('react');
var ReactDOM = require('react-dom');
import TodoBox from './components/todobox';
import './../css/index.css';

export default class Index extends React.Component {
  constructor(){
    super();
  };
  render() {
    return (
        <TodoBox />
    );
  }
}

ReactDOM.render(<Index/>,document.getElementById("example"))

TodoItem

让我们想想啊,对于每一条内容来说,需要什么呢?

  • 一个确认是否完成的checkbox [ ]
  • 一条内容text
  • 一个删除button
  • zzzzzz.....其他的暂时先不加了~

那不是太简单了 ~

<li>
  <input type="checkbox"/>找工作啊找工作啊
  <button>删除</button>
</li>

不不不,我们现在是在写React,要这样:

import React from 'react';
import {Row, Col, Checkbox, Button} from 'antd';

export default class TodoItem extends React.Component {
  constructor(props) {
    super(props)
    this.toggleComplete = this.toggleComplete.bind(this)
    this.deleteTask = this.deleteTask.bind(this)
  }
  toggleComplete() {
    this.props.toggleComplete(this.props.taskId)
  }
  deleteTask() {
    this.props.deleteTask(this.props.taskId)
  }
  render() {
    let task = this.props.task
    let itemChecked
    if (this.props.complete === "true") {
      task = <del>{task}</del>
      itemChecked = true
    } else {
      itemChecked = false
    }
    return (
      <li className="list-group-item">
        <Row>
          <Col span={12}>
            <Checkbox checked={itemChecked} onChange={this.toggleComplete}/> {task}
          </Col>
          <Col span={12}>
            <Button type="danger" className="pull-right" onClick={this.deleteTask}>删除</Button>
          </Col>
        </Row>
      </li>
    )
  }
}

import {Row, Col, Checkbox, Button} from 'antd'是引入Ant Design。

我们采用 React 封装了一套 Ant Design 的组件库,也欢迎社区其他框架的实现版本。

引入这个之后,我们可以直接使用一些简单的UI组件,比如Row,Col,Checkbox,Button等,我们可以更加注重业务逻辑的实现。

TodoList

接下来就是拿一个<ul></ul>把item包起来呗:

import React from 'react';
import TodoItem from './todoitem';
export default class TodoList extends React.Component{
  constructor(props) {
    super(props);
  }
  render(){
    var taskList=this.props.data.map(listItem=>
      <TodoItem taskId={listItem.id}
                key={listItem.id}
                task={listItem.task}
                complete={listItem.complete}
                toggleComplete={this.props.toggleComplete}
                deleteTask={this.props.deleteTask}/>
    )
    return(
      <ul className="list-group">
        {taskList}
      </ul>
    )
  }
}

AddTodoItem

添加内容这个组件也比较简单,就只需要一个input和一个button即可:

import React from 'react';
import ReactDOM from 'react-dom';
import {Row, Col, Form, Input, Button,notification } from 'antd';
export default class AddTodoItem extends React.Component {
  constructor(props) {
    super(props)
    this.saveNewItem = this.saveNewItem.bind(this)
  }
  saveNewItem(e) {
    e.preventDefault()
    let element = ReactDOM.findDOMNode(this.refs.newItem)
    let task = element.value
    if (!task) {
      notification.open({
        description: 'Todo内容不得为空!',
    });
    } else {
      this.props.saveNewItem(task)
      element.value = ""
    }
  }
  render() {
    return (
      <div className="addtodoitem">
        <Form.Item>
          <label htmlFor="newItem"></label>
          <Input id="newItem" ref="newItem" type="text" placeholder="吃饭睡觉打豆豆~"></Input>
          <Button type="primary" className="pull-right" onClick={this.saveNewItem}>保存</Button>
        </Form.Item>
      </div>
    )
  }
}

TodoBox

我们的小组件已经都实现了,拿一个大box包起来呗 ~

import React from 'react';
import TodoList from './todolist';
import AddTodoItem from './addtodoitem';
import {Button, Icon, Row, Col} from 'antd';
export default class TodoBox extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data: [
        {
          "id": "1",
          "task": "做一个TodoList Demo",
          "complete": "false"
        }, {
          "id": "2",
          "task": "学习ES6",
          "complete": "false"
        }, {
          "id": "3",
          "task": "Hello React",
          "complete": "true"
        }, {
          "id": "4",
          "task": "找工作",
          "complete": "false"
        }
      ]
    }
    this.handleToggleComplete = this.handleToggleComplete.bind(this);
    this.handleTaskDelete = this.handleTaskDelete.bind(this);
    this.handleAddTodoItem = this.handleAddTodoItem.bind(this);
  }
  generateGUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random() * 16 | 0,
        v = c == 'x' ? r : (r & 0x3 | 0x8)
      return v.toString(16)
    })
  }
  handleToggleComplete(taskId) {
    let data = this.state.data;
    for (let item of data) {
      if (item.id === taskId) {
        item.complete = item.complete === "true" ? "false" : "true"
      }
    }
    this.setState({data})
  }
  handleTaskDelete(taskId) {
    let data = this.state.data
    data = data.filter(task => task.id !== taskId)
    this.setState({data})
  }
  handleAddTodoItem(task) {
    let newItem = {
      id: this.generateGUID(),
      task,
      complete: "false"
    }
    let data = this.state.data
    data = data.concat([newItem])
    this.setState({data})
  }
  render() {
    return (
      <div>
        <div className="well">
          <h1 className="text-center">React TodoList</h1>
          <TodoList data={this.state.data} toggleComplete={this.handleToggleComplete} deleteTask={this.handleTaskDelete}/>
          <AddTodoItem saveNewItem={this.handleAddTodoItem}/>
        </div>
        <Row>
          <Col span={12}></Col>
          <Col span={12}>
            <Button className="pull-left"><Icon type="user"/>
              <a href="http://axuebin.com">薛彬</a>
            </Button>
            <Button className="pull-right"><Icon type="github"/>
              <a href="https://github.com/axuebin">axuebin</a>
            </Button>
          </Col>
        </Row>
      </div>
    )
  }
}

注意:

  • 通过props传递子组件需要的值和方法
  • 传递方法时一定要bind(this),不然内部this会指向不正确

源码

完整的Demo代码在这:https://github.com/axuebin/react-todolist

JavaScript this

看看这个有着深不可测的魔力的this到底是个什么玩意儿 ~


什么是this

在传统面向对象的语言中,比如Java,this关键字用来表示当前对象本身,或当前对象的一个实例,通过this关键字可以获得当前对象的属性和调用方法。

在JavaScript中,this似乎表现地略有不同,这也是让人“讨厌”的地方~

ECMAScript规范中这样写:

this 关键字执行为当前执行环境的 ThisBinding。

MDN上这样写:

In most cases, the value of this is determined by how a function is called.
在绝大多数情况下,函数的调用方式决定了this的值。

可以这样理解,在JavaScript中,this的指向是调用时决定的,而不是创建时决定的,这就会导致this的指向会让人迷惑,简单来说,this具有运行期绑定的特性。

参考资料:this - JavaScript | MDN

来看看不同的情况五花八门的this吧~

调用位置

首先需要理解调用位置,调用位置就是函数在代码中被调用的位置,而不是声明的位置。

通过分析调用栈(到达当前执行位置所调用的所有函数)可以找到调用位置。

function baz(){
  console.log("baz");
  bar();
}
function bar(){
  console.log("bar");
  foo();
}
function foo(){
  console.log("foo");
}
baz();

当我们调用baz()时,它会以此调用baz()bar()foo()

对于foo():调用位置是在bar()中。
对于bar():调用位置是在baz()中。
而对于baz():调用位置是全局作用域中。

可以看出,调用位置应该是当前正在执行的函数的前一个调用中。

全局上下文

在全局执行上下文中this都指代全局对象。

  • this等价于window对象
  • var === this. === winodw.
console.log(window === this); // true
var a = 1;
this.b = 2;
window.c = 3;
console.log(a + b + c); // 6

在浏览器里面this等价于window对象,如果你声明一些全局变量,这些变量都会作为this的属性。

函数上下文

在函数内部,this的值取决于函数被调用的方式。

直接调用

this指向全局变量。

function foo(){
  return this;
}
console.log(foo() === window); // true

call()、apply()

this指向绑定的对象上。

var person = {
  name: "axuebin",
  age: 25
};
function say(job){
  console.log(this.name+":"+this.age+" "+job);
}
say.call(person,"FE"); // axuebin:25
say.apply(person,["FE"]); // axuebin:25

可以看到,定义了一个say函数是用来输出nameagejob,其中本身没有nameage属性,我们将这个函数绑定到person这个对象上,输出了本属于person的属性,说明此时this是指向对象person的。

如果传入一个原始值(字符串、布尔或数字类型)来当做this的绑定对象, 这个原始值会被转换成它的对象形式(new String()),这通常被称为“装箱”。

callapplythis的绑定角度上来说是一样的,唯一不同的是它们的第二个参数。

bind()

this将永久地被绑定到了bind的第一个参数。

bindcallapply有些相似。

var person = {
  name: "axuebin",
  age: 25
};
function say(){
  console.log(this.name+":"+this.age);
}
var f = say.bind(person);
console.log(f());

箭头函数

所有的箭头函数都没有自己的this,都指向外层。

关于箭头函数的争论一直都在,可以看看下面的几个链接:

ES6 箭头函数中的 this?你可能想多了(翻译)

关于箭头函数this的理解几乎完全是错误的 #150

MDN中对于箭头函数这一部分是这样描述的:

An arrow function does not create its own this, the this value of the enclosing execution context is used.
箭头函数会捕获其所在上下文的this值,作为自己的this值。

function Person(name){
  this.name = name;
  this.say = () => {
    var name = "xb";
    return this.name;
  }
}
var person = new Person("axuebin");
console.log(person.say()); // axuebin

箭头函数常用语回调函数中,例如定时器中:

function foo() {  
  setTimeout(()=>{
    console.log(this.a);
  },100)
}
var obj = {
  a: 2
}
foo.call(obj);

附上MDN关于箭头函数this的解释:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions#不绑定_this

作为对象的一个方法

this指向调用函数的对象。

var person = {
  name: "axuebin",
  getName: function(){
    return this.name;
  }
}
console.log(person.getName()); // axuebin

这里有一个需要注意的地方。。。

var name = "xb";
var person = {
  name: "axuebin",
  getName: function(){
    return this.name;
  }
}
var getName = person.getName;
console.log(getName()); // xb

发现this又指向全局变量了,这是为什么呢?

还是那句话,this的指向得看函数调用时。

作为一个构造函数

this被绑定到正在构造的新对象。

通过构造函数创建一个对象其实执行这样几个步骤:

  1. 创建新对象
  2. 将this指向这个对象
  3. 给对象赋值(属性、方法)
  4. 返回this

所以this就是指向创建的这个对象上。

function Person(name){
  this.name = name;
  this.age = 25;
  this.say = function(){
    console.log(this.name + ":" + this.age);
  }
}
var person = new Person("axuebin");
console.log(person.name); // axuebin
person.say(); // axuebin:25

作为一个DOM事件处理函数

this指向触发事件的元素,也就是始事件处理程序所绑定到的DOM节点。

var ele = document.getElementById("id");
ele.addEventListener("click",function(e){
  console.log(this);
  console.log(this === e.target); // true
})

HTML标签内联事件处理函数

this指向所在的DOM元素

<button onclick="console.log(this);">Click Me</button>

jQuery的this

在许多情况下JQuery的this都指向DOM元素节点。

$(".btn").on("click",function(){
  console.log(this); 
});

总结

如果要判断一个函数的this绑定,就需要找到这个函数的直接调用位置。然后可以顺序按照下面四条规则来判断this的绑定对象:

  1. new调用:绑定到新创建的对象
  2. callapplybind调用:绑定到指定的对象
  3. 由上下文对象调用:绑定到上下文对象
  4. 默认:全局对象

注意:箭头函数不使用上面的绑定规则,根据外层作用域来决定this,继承外层函数调用的this绑定。

JavaScript call apply bind

整理callapplybind这三个方法的的知识点。


之前这篇文章提到过this的各种情况,其中有一种情况就是通过callapplybind来将this绑定到指定的对象上。

也就是说,这三个方法可以改变函数体内部this的指向。

这三个方法有什么区别呢?分别适合应用在哪些场景中呢?

先举个简单的栗子 ~

var person = {
  name: "axuebin",
  age: 25
};
function say(job){
  console.log(this.name+":"+this.age+" "+job);
}
say.call(person,"FE"); // axuebin:25 FE
say.apply(person,["FE"]); // axuebin:25 FE
var sayPerson = say.bind(person,"FE");
sayPerson(); // axuebin:25 FE

对于对象person而言,并没有say这样一个方法,通过call/apply/bind就可以将外部的say方法用于这个对象中,其实就是将say内部的this指向person这个对象。

call

call是属于所有Function的方法,也就是Function.prototype.call

The call() method calls a function with a given this value and arguments provided individually.

call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

它的语法是这样的:

fun.call(thisArg[,arg1[,arg2,]]);

其中,thisArg就是this指向,arg是指定的参数。

call的用处简而言之就是可以让call()中的对象调用当前对象所拥有的function。

ECMAScript规范

ECMAScript规范中是这样定义call的:

当以thisArg和可选的arg1,arg2等等作为参数在一个func对象上调用call方法,采用如下步骤:

  1. 如果IsCallable(func)false, 则抛出一个TypeError异常。
  2. argList为一个空列表。
  3. 如果调用这个方法的参数多余一个,则从arg1开始以从左到右的顺序将每个参数插入为argList的最后一个元素。
  4. 提供thisArg作为this值并以argList作为参数列表,调用func[[Call]]内部方法,返回结果。

call方法的length属性是1。

在外面传入的thisArg值会修改并成为this值。thisArgundefinednull时它会被替换成全局对象,所有其他值会被应用ToObject并将结果作为this值,这是第三版引入的更改。

使用call调用函数并且指定this

var obj = {
  a: 1
}
function foo(b, c){
  this.b = b;
  this.c = c;
  console.log(this.a + this.b + this.c);
}
foo.call(obj,2,3); // 6

call实现继承

在需要实现继承的子类构造函数中,可以通过call调用父类构造函数实现继承。

function Person(name, age){
  this.name = name;
  this.age = age;
  this.say = function(){
    console.log(this.name + ":" + this.age);
  }
}
function Student(name, age, job){
  Person.call(this, name ,age);
  this.job = job;
  this.say = function(){
    console.log(this.name + ":" + this.age + " " + this.job);
  }
}
var me = new Student("axuebin",25,"FE");
console.log(me.say()); // axuebin:25 FE

apply

apply也是属于所有Function的方法,也就是Function.prototype.apply

The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。

它的语法是这样的:

fun.apply(thisArg, [argsArray]);

其中,thisArg就是this指向,argsArray是指定的参数数组。

通过语法就可以看出callapply的在参数上的一个区别:

  • call的参数是一个列表,将每个参数一个个列出来
  • apply的参数是一个数组,将每个参数放到一个数组中

ECMAScript规范

当以thisArgargArray为参数在一个func对象上调用apply方法,采用如下步骤:

  1. 如果IsCallable(func)false, 则抛出一个TypeError异常 .
  2. 如果argArraynullundefined, 则
  3. 返回提供thisArg作为this值并以空参数列表调用func[[Call]]内部方法的结果。
  4. 如果Type(argArray)不是Object, 则抛出一个TypeError异常 .
  5. len为以"length"作为参数调用argArray[[Get]]内部方法的结果。
  6. nToUint32(len).
  7. argList为一个空列表 .
  8. index为0.
  9. 只要index<n就重复
  10. indexNameToString(index).
  11. nextArg为以indexName作为参数调用argArray[[Get]]内部方法的结果。
  12. nextArg作为最后一个元素插入到argList里。
  13. 设定indexindex + 1.
  14. 提供thisArg作为this值并以argList作为参数列表,调用func[[Call]]内部方法,返回结果。

apply方法的length属性是 2。

在外面传入的thisArg值会修改并成为this值。thisArgundefinednull时它会被替换成全局对象,所有其他值会被应用ToObject并将结果作为this值,这是第三版引入的更改。

用法

在用法上applycall一样,就不说了。

实现一个apply

参考链接:jawil/blog#16

第一步,绑定上下文

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取
  context.fn = this;
  // 执行这个函数
  context.fn();
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(){
    console.log(this.name);
  }
}

var me = {
  name: "axuebin"
}

obj.getName(); // xb 
obj.getName.myApply(me); // axuebin

确实成功地将this指向了me对象,而不是本身的obj对象。

第二步,给定参数

上文已经提到apply需要接受一个参数数组,可以是一个类数组对象,还记得获取函数参数可以用arguments吗?

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取
  context.fn = this;
  // 通过arguments获取参数
  var args = arguments[1];
  // 执行这个函数,用ES6的...运算符将arg展开
  context.fn(...args);
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var me = {
  name: "axuebin"
}

obj.getName(); // xb:undefined
obj.getName.myApply(me,[25]); // axuebin:25

context.fn(...arg)是用了ES6的方法来将参数展开,如果看过上面那个链接,就知道这里不通过...运算符也是可以的。

原博主通过拼接字符串,然后用eval执行的方式将参数传进context.fn中:

for (var i = 0; i < args.length; i++) {
  fnStr += i == args.length - 1 ? args[i] : args[i] + ',';
}
fnStr += ')';//得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行
eval(fnStr); //还是eval强大

第三步,当传入apply的this为null或者为空时

我们知道,当apply的第一个参数,也就是this的指向为null时,this会指向window。知道了这个,就简单了~

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window
  var context = context || window;
  context.fn = this;
  //获取传入的数组参数
  var args = arguments[1];
  if (args == undefined) { //没有传入参数直接执行
    // 执行这个函数
    context.fn()
  } else {
    // 执行这个函数
    context.fn(...args);
  }
  // 从上下文中删除函数引用
  delete context.fn;
}

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var name = "window.name";

var me = {
  name: "axuebin"
}

obj.getName(); // xb:25
obj.getName.myApply(); // window.name:undefined
obj.getName.myApply(null, [25]); // window.name:25
obj.getName.myApply(me, [25]); // axuebin:25

第四步 保证fn函数的唯一性

ES6中新增了一种基础数据类型Symbol

const name = Symbol();
const age = Symbol();
console.log(name === age); // false

const obj = {
  [name]: "axuebin",
  [age]: 25
}

console.log(obj); // {Symbol(): "axuebin", Symbol(): 25}
console.log(obj[name]); // axuebin

所以我们可以通过Symbol来创建一个属性名。

var fn = Symbol();
context[fn] = this;

完整的apply

Function.prototype.myApply=function(context){
  // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window
  var context = context || window;
  var fn = Symbol();
  context[fn] = this;
  //获取传入的数组参数
  var args = arguments[1];
  if (args == undefined) { //没有传入参数直接执行
    // 执行这个函数
    context[fn]()
  } else {
    // 执行这个函数
    context[fn](...args);
  }
  // 从上下文中删除函数引用
  delete context.fn;
}

这样就是一个完整的apply了,我们来测试一下:

var obj ={
  name: "xb",
  getName: function(age){
    console.log(this.name + ":" + age);
  }
}

var name = "window.name";

var me = {
  name: "axuebin"
}

obj.getName(); // xb:25
obj.getName.myApply(); // window.name:undefined
obj.getName.myApply(null, [25]); // window.name:25
obj.getName.myApply(me, [25]); // axuebin:25

ok 没啥毛病 ~

再次感谢1024大佬 ~

bind

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

语法:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

其中,thisArg就是this指向,arg是指定的参数。

可以看出,bind会创建一个新函数(称之为绑定函数),原函数的一个拷贝,也就是说不会像callapply那样立即执行。

当这个绑定函数被调用时,它的this值传递给bind的一个参数,执行的参数是传入bind的其它参数和执行绑定函数时传入的参数。

用法

当我们执行下面的代码时,我们希望可以正确地输出name,然后现实是残酷的

function Person(name){
  this.name = name;
  this.say = function(){
    setTimeout(function(){
      console.log("hello " + this.name);
    },1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello undefined

这里this运行时是指向window的,所以this.nameundefined,为什么会这样呢?看看MDN的解释:

由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window。

有一个常见的方法可以使得正确的输出:

function Person(name){
  this.name = name;
  this.say = function(){
    var self = this;
    setTimeout(function(){
      console.log("hello " + self.name);
    },1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello axuebin

没错,这里我们就可以用到bind了:

function Person(name){
  this.name = name;
  this.say = function(){
    setTimeout(function(){
      console.log("hello " + this.name);
    }.bind(this),1000)
  }
}
var person = new Person("axuebin");
person.say(); //hello axuebin

MDN的Polyfill

Function.prototype.bind = function (oThis) {
  var aArgs = Array.prototype.slice.call(arguments, 1)
  var fToBind = this;
  var fNOP = function () {}
  var fBound = function () {
    fBound.prototype = this instanceof fNOP ? new fNOP() : fBound.prototype;
    return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs )
  }   
  if( this.prototype ) {
    fNOP.prototype = this.prototype;
  }
  return fBound;
}

总结

  • 三者都是用来改变函数的this指向
  • 三者的第一个参数都是this指向的对象
  • bind是返回一个绑定函数可稍后执行,callapply是立即调用
  • 三者都可以给定参数传递
  • call给定参数需要将参数全部列出,apply给定参数数组

感谢

不用call和apply方法模拟实现ES5的bind方法

深入浅出妙用 Javascript 中 apply、call、bind

回味JS基础:call apply 与 bind

React的生命周期到底是怎么一回事?

尽量全面详细的整理一下React的生命周期中的知识点。


组件

组件是独立的封装的可以复用的一个小部件,它是React的核心**之一。通过划分组件,可以将一个页面划分成独立的多个可复用的组件,各个组件通过嵌套、组合形成一个完整的页面。

在React中,组件基本由三个部分组成:属性(props)、状态(state)以及生命周期方法。可以将组件简单地看作一个“状态机”,根据不同的stateprops呈现不同的UI,通过与用户的交互实现不同的状态,然后重新渲染组件,UI可以跟随数据变化而变化。

创建组件

组件常分为两种:Class ComponentFunctional Component

无状态组件

Functional Component也称为无状态组件,它多用于纯展示组件,这种组件只负责根据传入的props来渲染组件,而不涉及state状态管理。

在大部分React代码中,大多数组件被写成无状态的组件,通过简单组合可以构建成其他的组件等;这种通过多个简单然后合并成一个大应用的设计模式被提倡。

无状态组件可以通过函数形式或者ES6的箭头函数来创建:

// 函数
function HelloFunctional(props){
  return <div>hello {props.name}</div>;
}

// ES6箭头函数
const HelloFunctional = (props) => (<div>hello {props.name}</div>);

无状态组件有以下几个特点:

  1. 代码可读性更好
  2. 组件不会被实例化,渲染性能提升
  3. 无生命周期方法
  4. 只能输入props,同样的输入一定会有同样的输出

所以,在项目中如果不需要进行状态管理,应该尽量写成无状态组件的形式。

有状态组件

现在主流的创建有状态组件的形式是通过ES6的Class来创建,取代React.createClass

Class HelloClass extends React.Component{
  constructor(){
    this.state = {
      name:'axuebin'
    }
  }
  render(){
    return (<div>hello {this.state.name}</div>);
  }
}

这是最简洁的一个组件,它需要使用到内部状态state

当组件需要使用内部状态时或者需要使用生命周期方法时就需要使用有状态组件。

组件的生命周期

React组件的生命周期可以分为挂载、渲染和卸载这几个阶段,当渲染后的组件更新后,会重新渲染组件,直到卸载。先分阶段来看看每个阶段有哪些生命周期函数。

挂载阶段(Mounting)

属于这个阶段的生命周期函数有:

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

constructor()

constructor() {
  super();
  this.state = {name: 'axuebin'};
  this.handleClick = this.handleClick.bind(this); 
}

这个阶段就是组件的初始化,constructor()可以理解为组件的构造函数,从组件的类class实例化一个组件实例。这个函数是组件形成时就被调用的,是生命周期中最先执行的。

constructor()函数内,首先必须执行super(),否则this.props将是未定义,会引发异常。

然后,如果有必要,可以进行:

  • state的初始化
  • 方法的绑定

如果不需要这两步,可以直接省略constructor函数。

componentWillMount()

这个函数按照驼峰法的命名规则可以理解为“组件即将被挂载”,所以这个函数是组件首次渲染(render)前调用的。

在每次页面加载、刷新时,或者某个组件第一次展现时都会调用这个函数。通常地,我们推荐使用constructor()来替代。

注意:在这个函数中,不可以调用setState来修改状态。

render()

render() {
  return(
    <div>hello {this.state.name} {this.props.age}</div>
  )
}

render()在生命周期中是必须的,是渲染组件用的。

当这个函数被调用时,需要检查this.propsthis.state并且返回一个元素(有且只有一个元素),这个元素可能是一个原生DOM元素,也有可能是另一个React组件。

可以在stateprops状态为空时试着返回一个null或者false来声明不想渲染任何东西。

在这个函数中,不应该改变组件的状态,也就是不执行this.setState,需要保持render()函数的纯净。

在这个函数中,可以对props进行调用并组合,但不可修改。

componentDidMount()

componentDidMount() {
  this.setState({name:'xb'});
}

这个函数在组件加载渲染完成后立即调用,此时页面上已经渲染出真实的DOM了,可以在这个函数中访问到真实的DOM(可以通过this.refs来访问真实DOM)。

在这个阶段,还可以做一件事,可以修改state了!!!

而且,异步获取数据在这个阶段执行也是比较合理的,获取数据之后setState,然后重新渲染组件。

更新阶段(Updating)

属性或状态的改变会触发一次更新。当一个组件在被重新渲染时,这些方法将会被调用:

  1. componentWillReceiveProps()
  2. shouldComponentUpdate()
  3. componentWillUpdate()
  4. render()
  5. componentDidUpdate()

componentWillReceiveProps()

已加载的组件在props发生变化时调用,若需要更新状态,可能需要对比this.propsnextProps然后在该方法中使用this.setState来处理状态的改变。

需要注意的是,有些情况下,即使props未改变也会触发该函数,所以一定要先比较this.propsnextProps再做操作。

该函数只监听props的改变,this.setState不会触发这个函数。

componentWillReceiveProps(nextProps){
  if (this.props.color !== nextProps.color){
    this.setState({});
  }
}

shouldComponentUpdate()

这个函数只返回truefalse,表示组件是否需要更新(重新渲染)。

  1. 返回true就是紧接着以下的生命周期函数;
  2. 返回false表示组件不需要重新渲染,不再执行任何生命周期函数(包括render)。

这个函数使用需谨慎,react官方文档中说道,在未来这个函数返回false可能仍然使得组件重新渲染。

componentWillUpdate()

这个函数看名字就和componentWillMount很像,它执行的阶段也很像。在接收到新的props或者state之后,这个函数就会在render前被调用。

同样的,在这个函数中不能使用this.setState()。如果需要更新状态,请在componentWillReceiveProps中调用this.setState()

render()

又是一次的render。这和挂载阶段的render有什么区别呢?

在函数的性质上来说,两者毫无区别,只不过是在生命周期的不同阶段的调用。

  • 前一个render是在组件第一次加载时调用的,也就是初次渲染,可以理解为mount
  • 后一个render是除去第一次之后调用的,也就是再渲染,re-render

componentDidUpdate()

同样地,这个方法是在组件re-render之后调用的,该方法不会在初始化的时候调用。和componentDidMount一样,在这个函数中可以使用this.refs获取真实DOM。

还可以修改state哦,不过会导致组件再次re-render

卸载阶段(Unmounting)

该方法将会在 component 从DOM中移除时调用

  • componentWillUnmount()

componentWillUnmount()

卸载阶段就很简单了,就这一个生命周期函数,在组件被卸载和销毁之前立刻调用。

在这个函数中,应该处理任何必要的清理工作,比如销毁定时器、取消网络请求、清除之前创建的相关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.