React + webpack + redux 后台系统 Demo 代码阅读 (一)

最近在自学React,在git上找到一个demo,打算用日志的形式记录阅读代码的过程。
使用的技术:

  • React
  • webpack
  • redux
  • antd
  • axios
  • less

本人对前端的知识仅限于HTML CSS JS 且都是基础阶段。 在公司项目中写过简单的页面。
对进阶的知识涉猎有限。本次希望以一边阅读代码一边在不懂的地方跳出进行知识补充的方法学习。
因此遇到不知道的地方我会用跳出关键词暂且离开代码片段。
如果有理解不对的地方希望大家能够指正教导。

项目结构图
|-app
|  |-apis
|  |-components
|  |-configs
|  |-images
|  |-middleware
|  |-mocks
|  |-pages
|  |-redux
|  |-resource
|  |-styles
|  |-client.js
|  |-index.html
|
|-scrips

结构分析

apis:接口
components:组件
configs:配置文件
middleware:中间件
mocks:mock数据
pages:页面
redux:redux文件
resource:资源
styles:样式
client.js:热加载文件
index.html:主页
scrips:webpack的配置文件

由于目前我只对React有了解,所以先从react的组件开始看起。

draw.js

这是一个抽屉组件,具体是点击一个按钮(如创建用户)之后,从页面的侧边类似抽屉一样划出。
这种效果比较普遍,与Popup相比更加亲和一点

先看文件头
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types';
import './draw.less'

其中,React是react的核心代码, ReactDOM是react涉及DOM操作的部分,这两个暂且不提。

PropTypes用来对组件的props进行类型检测,跳出

PropTypes介绍 参考Iam_Bling@简书

安装

npm install --save prop-types

使用

import PropTypes from 'prop-types';
const Auth = ({match, isLoggedIn}) => {

};
Auth.propTypes = {
    match: PropTypes.object, // 对象
    isLoggedIn: PropTypes.bool, // 布尔值
};

propTypes能用来检测全部数据类型的变量:
-array 数组
-bool 布尔值
-func 函数
-number 数值
-object 对象
-string 字符串
-symbol symbol类型

多类型
如果一个prop支持两种类型的话,可以用oneOfType实现

Auth.propTypes = {
    number: PropTypes.oneOfType(
        [PropTypes.string,PropTypes.number]
        )
};

支持多个值
oneOf指定多个变量值

Auth.propTypes = {
    number: PropTypes.oneOf(
        [12,24,35]
    )
};

检测对象不同属性的不同类型
如果要检测数组中的元素或者对象中的属性,需要用到arrayOf

Auth.propTypes = {
    array:PropTypes.arrayOf(PropTypes.number)
};

检测对象不同属性的不同类型
brandList是一个数组类型,其中每一个元素都是对象类型,分别对每个对象进行检测

Auth.propTypes = {
    brandsList:PropTypes.arrayOf(
        PropTypes.shape({
            id: PropTypes.number.isRequired,
            name: PropTypes.string.isRequired,
            keywords: PropTypes.string.isRequired,
            excludeKeywords: PropTypes.string,
            sites: PropTypes.string.isRequired,
            status: PropTypes.string,
            }),
    ).isRequired,
    isBrandsListLoaded: PropTypes.bool.isRequired
};

必须属性
用isRequired定义一个变量是必须的

Auth.propTypes = {
    match: PropTypes.object.isRequired,
    isLoggedIn: PropTypes.bool.isRequired,
};

跳回
export default class Drawer extends Component
模块对外输出这个组件,export和export default的区别在于export default会输出叫做default的变量,
可以给这个default取任何名字,import命令后面不用大括号

构造器部分

这边设置了几个state值,dreawTrasformClass、maskTrasformClass、drawerSizeClass(默认modal-base)

组件生命周期

这里涉及了几个不同的React组件生命周期,跳出

参考 不写代码的码农@segmentfault
实例化阶段

当组件在客户端被实例化,第一次被创建时,以下方法依次被调用:

1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
5、componentDidMount

当组件在服务端被实例化,首次被创建时,以下方法依次被调用:

1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render

componentDidMount 不会在服务端被渲染的过程中调用。

1.getDefaultProps
对于每个组件实例来讲,这个方法只会调用一次,该组件类的所有后续应用,
getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props(properties的缩写) 值。

var Hello = React.creatClass({
    getDefaultProps: function(){
        return {
            name: 'pomy',
            git: 'dwqs'
        }
    },
    
    render: function(){
        return (
            <div>Hello,{this.props.name},git username is {this.props.dwqs}</div>
        )
    }
});

ReactDOM.render(<Hello />, document.body);

2.getInitialState
对于组件的每个实例,此方法只调用一次,用来初始化每个实例的state,
与getDefaultProps的区别是,Props对于组件类只调用一次,State在每个实例都会调用一次

var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

每次修改 state,都会重新渲染组件,实例化后通过 state 更新组件,会依次调用下列方法:

1、shouldComponentUpdate
2、componentWillUpdate
3、render
4、componentDidUpdate

componentWillMount
该方法在首次渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。

render
该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:

-只能通过 this.props 和 this.state 访问数据(不能修改)
-可以返回 null,false 或者任何React组件
-只能出现一个顶级组件,不能返回一组元素
-不能改变组件的状态
-不能修改DOM的输出
render方法返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象。

componentDidMount
该方法不会在服务端被渲染的过程中调用。
该方法被调用时,已经渲染出真实的 DOM,
可以在该方法中通过 this.getDOMNode()访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode())。

在组件的存在期,可以与其他事件交互的时候,又会存在以下的周期

1、componentWillReceiveProps
2、shouldComponentUpdate
3、componentWillUpdate
4、render
5、componentDidUpdate

componentWillReceiveProps
组件的 props 属性可以通过父组件来更改,这时,componentWillReceiveProps 将来被调用。可以在这个方法里更新 state,以触发 render 方法重新渲染组件。

componentWillReceiveProps: function(nextProps){
    if(nextProps.checked !== undefined){
        this.setState({
            checked: nextProps.checked
        })
    }
}

shouldComponentUpdate
如果你确定组件的 props 或者 state 的改变不需要重新渲染,可以通过在这个方法里通过返回 false 来阻止组件的重新渲染,返回 false 则不会执行 render 以及后面的 componentWillUpdate,componentDidUpdate 方法。

该方法是非必须的,并且大多数情况下没有在开发中使用。

shouldComponentUpdate: function(nextProps, nextState){
    return this.state.checked === nextState.checked;
    //return false 则不更新组件
}

componentWillUpdate
这个方法和 componentWillMount 类似,在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用,注意不要在此方面里再去更新 props 或者 state。

componentDidUpdate
这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。

销毁时

componentWillUnmount
每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。

当再次装载组件时,以下方法会被依次调用:

1、getInitialState
2、componentWillMount
3、render
4、componentDidMount

跳回

componentWillMount() {
    document.body.classList.add('body-drawer')
  }

在组件加载前,先在classList中添加一个’body-drawer’的类,classList是为了方便添加和删除类


  // 组件已经加载到dom中
  componentDidMount() {
    const {
      visible = true,
    } = this.props
    if (visible) {
      this.initDrawer()
    }
    const {
      size = 'default',
    } = this.props
    this.getDrawerSize(size)
    this.setTrasformClass()
  }

加载完组件之后执行的代码,将visible和size设定一下,如果visible,执行initDrawer初始化抽屉class,再执行setTrasformClass()
其中

const {visible = true,} = this.props

等同于

const visible = this.props.visible || true

如果传递过来的prop没有visible的话,默认为true

看一下初始化抽屉initDrawer方法,

  initDrawer = () => {
    this.popup = document.createElement('div')
    this.popup.setAttribute('class', 'drawers')
    document.body.appendChild(this.popup)
    this.renderDrawer()
    this.setTrasformClass()
  }

新建一个弹出层,然后append到body中,最后调用renderDrawer和setTrasformClass方法
renderDrawer方法:

renderDrawer(_class = {}) {
    const {
      title = '标题',
      footer = null,
    } = this.props
    const {
      drawerSizeClass,
    } = this.state

    // 优先使用自己定义的class动画
    const drawTrasformClass = _class.drawTrasformClass || this.state.drawTrasformClass
    const maskTrasformClass = _class.maskTrasformClass || this.state.maskTrasformClass

    ReactDOM.render(
      <div className="drawer-wrap">
        // 撤回抽屉区块,点击调用removeDrawer方法
        <div className={`${maskTrasformClass} ant-modal-mask`} onClick={() => this.removeDrawer()} />
        // 抽屉块
        <div className={`${drawTrasformClass} draw ${drawerSizeClass} ${this.props.className}`}>
          <div className="ant-modal">
            <div className="ant-modal-content">
            // 点击关闭按钮同样调用removeDrawer方法关闭抽屉
              <button className="ant-modal-close">
                <span className="ant-modal-close-x" onClick={() => this.removeDrawer()} />
              </button>
              // 标题块
              <div className="ant-modal-header">
                <div className="ant-modal-title">{title}</div>
              </div>
              // 内容块
              <AntModalBody context={this.context}>
              // 此处为调用Drawer的作用域的children内容(把children内容放到这个抽屉里面)
                {this.props.children}
              </AntModalBody>
              // 如果有footer的话render出footer
              {
                footer ?
                  <div className="ant-modal-footer">
                    {footer}
                  </div> : null
              }
            </div>
          </div>
        </div>
      </div>,
      // 之前初始化的抽屉在这里输出 是一个class='drawer'的div区块->此处意义不明,待调查
      this.popup,
    )
  }

setTrasformClass()方法,

  setTrasformClass = () => {
    const {
      visible = true,
    } = this.props
    if (visible) {
      this.setState({
        drawTrasformClass: 'draw-enter',
        maskTrasformClass: 'mask-enter',
      }, () => {
        setTimeout(() => {
          this.setState({
            drawTrasformClass: '',
            maskTrasformClass: '',
          })
        }, 300)
      })
    }
  }

其中setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式。
当drawer可见时,先setState做一个抽屉拉开的效果,然后隔三秒之后把拉开效果重置为’’

到这边为止抽屉组件就已经渲染完成了,接下来进入到组建的存在期,检测visible属性,如果visible变更
则会触发componentUpdate事件,这边在willUpdate和render没有什么特殊处理,直接跳到componentDidUpdate方法

  componentDidUpdate() {
    const {
      visible = true,
    } = this.props
    if (visible) {
      this.renderDrawer()
    }
  }

方法还是调用一遍renderDrawer重新render一下抽屉

在组件销毁的时候会走到componentWillUnmount的方法,这边和componentDidMount方法不同的地方是会把transformClass设置成leave,
做一个抽屉离开的效果,然后3秒之后在classList中把这个Class给remove掉,并且unmountComponent

  componentWillUnmount() {
    this.renderDrawer({
      drawTrasformClass: 'draw-leave',
      maskTrasformClass: 'mask-leave',
    })
    setTimeout(() => {
      // document.body.removeChild(this.popup)
      document.body.classList.remove('body-drawer')
      ReactDOM.unmountComponentAtNode(this.popup)
    }, 300) // 组件即将卸载掉,执行动画再卸载
  }

剩下的就是检测Drawer、AntModalBody的propTypes,以及一个AntModalBody类,返回一个子javax区块
Ant Design目前还没有学习,之后会补充博文说明。

Drawer.contextTypes = {
  form: PropTypes.object,
  vertical: PropTypes.bool,
  store: PropTypes.object,
};
class AntModalBody extends Component {
  getChildContext() {
    return { form: this.props.context.form, vertical: this.props.context.vertical, store: this.props.context.store }
  }
  render() {
    return (
      <div className="ant-modal-body">
        {this.props.children}
      </div>
    )
  }
}
AntModalBody.childContextTypes = {
  form: PropTypes.object,
  vertical: PropTypes.string,
  store: PropTypes.object,
}

总结

Drawer组件比较好理解,分别在组件的不同生命周期中做相应的操作,关键节点在于ComponentDidMount和ComponentWillUnmount这两个阶段。
有一点不明确的地方是ComponentWillUnmount的操作和点击关闭或者点击到Mask区域的操作比较类似,
我的想法是点击关闭按钮和点击到mask区域是人为卸载drawer组件,用ReactDom操作unmountComponentAtNode,
其他的事件导致组件被卸载时会走到ComponentWillUnmount这个阶段里面执行应有的操作。
以上就是我对抽屉组件代码的解读

TODOS

学习Ant Design

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值