第一章 初入React世界(深入React技术栈)

第一章 初入React世界

重点词汇

  • 专注视图层

React并不是完整的MVC/MVVM框架,它专注于提供清晰、简介的view层解决方案。而又和模板引擎不同,因为它又是一个包括view和controller的库。

  • Virtual DOM

我们都知道DOM操作非常昂贵。在前端开发中,性能消耗最大的就是DOM操作。React把真实DOM树转换成JavaScript对象树,每次数据更新后,重新计算Virtual DOM,并和上次生成的VD作对比买对发生变化的部分做批量更新

  • 函数式编程

构建一个规则使计算重复被利用。React充分利用很多函数式方法减少用于代码。此外它本身就是简单函数,所以易于测试。可以说函数式编程才是React的精髓

  • JSX

jsx的官方定义是类XML语法的ECMAScript扩展。它完美地利用了js自带的语法和特性,并使用大家熟悉的HTML语法来穿件元素。
例如:

  const List = () => (
    <div>
      <Title>This is a title</Title>
      <ul>
        <li>list item</li>
        <li>list item</li>
        <li>list item</li>
      </ul>
    </div>
  )
复制代码

* 注意:定义标签时,只允许被一个标签包裹;标签一定要闭合;定义组件时命名应该使用大写字母开头!!!

元素属性:js中的class和for均为关键字,class->className;for->htmlFor,boolean属性例如:

<Checkbox checked={true} /> 
// 可以简写为
<Checkbox checked />
//反之checked为false就可以省略不写
<Checkbox checked={false}/>
//不知道props里与有什么就最好不要设置它,使用es6的rest/spread特性来提高效率
<Component {...data}>
复制代码
  • React组件
    代码实例详见p14-p15
    1、基本的封装性。
    2、简单的生命周期呈现
    3、明确的数据流动。根据不同的参数得到不同的响应,从而得出不同的渲染结果。

在这个阶段,前端在应用级别并没有过多复杂的交互,组件化发展缓慢。这样的逻辑一旦复杂起来,就会存在大量的dom操作,开发和维护成本相当高。

  • React组件的构建

React的本质就是关心元素的构成,它基本上由3个部分组成-----属性(props)、状态(state)以及生命周期方法

  • React组件的构建方法:1、使用React.createClass构建的组件时React最传统、也是兼容性最好的方法,例子:
const Button = React.createClass({
  getDefaultProps() {
    return {
      color: 'blue',
      text: 'Confirm'
    }
  }

  render() {
    const {text,color} = this.props;
    return (
      <button className={`btn btn-${color}`}>
        <em>{text}</em>
      </button>
    )
  }
})
复制代码

2、ES6 classes

import React,{Component} from 'react';
class Button extends Component {
  static defaultProps = {
    color: 'blue',
    text: 'Confirm'
  }
  render() {
    const {color,text} = this.props;
    return (
      <button className={`btn btn-${color}`}>
        <em>{text}</em>
      </button>
    )
  }
}
复制代码

在React的开发中,常用的方式是将组件拆分到合理的粒度,用组合的方式合成业务组件,也就是HAS-A关系。

* React的多有组件都继承自顶层React.Compoennt。它只是初始化了ReactComponent方法,声明了props、context、refs等,并在原型上定义了setState和forceUpdate方法。
3、无状态函数

无状态组件之传入props和context两个参数,不存在state,也没有生命周期方法。在适合的情况下,我们都应该切且必须使用无状态组件。

  • React的数据流

在React中,数据是自顶向下单向流动的。这条规则让组件之间的关系变得简单可测。

  • 内部的状态同意成为state。值得注意的是,setState是一个异步的方法,一个生命周期内所有的setState方法都会结并操作。我们并不推荐开发者滥用state,过多的内部状态会让数据流混乱,程序变得难以维护。
  • props 是React中用来让组件之间相互联系的的一种机制,就像方法的参数一样。对于一个tabs组件它的props会有那些呢,1、className,根节点class方便覆盖原始样式;
    2、classPrefix:class的前缀,对于组件来说,定义一个统一的class前缀,对样式与交互分离起了重要的作用
    3、defaultActiveIndex和activeIndex
    4、onChange:回调函数

  • 子组件prop 在React中有一个重要且内置的prop-children,它代表组件的子组件合计 p25

最后TabContent组件的render方法只需要调用getTabPanes方法渲染即可,

  render() {
    return (<div>{this.getTabPanes()}</div>)
  }
复制代码

这种调用方法叫做动态子组件(Dynamic Children)

  • 组件prop(component prop)
    可以将子组件以props的形式传递
  • 用function prop与父组件通信 this.props.onChange({activeIndex, preIndex})
    父组件将方法传递给子组件,子通过触发onChange给父亲回调需要的值
  • propTypes propTypes用来规范props类型的与必须的状态,常见的有(string、number、func、array、bool、oneOfType、node)

React的生命周期

React的组件的生命周期可以分为挂在、渲染和卸载这几个阶段。大体我们可以分为两类:1、当组件在挂在或卸载时;2、当组件接受新的数据时

  • 组件的挂载 componentDidMount componentWillMount
  • 组件的卸载 compoentWillUnmount
  • 数据的更新过程
  class App extends React.Component {
    componentWillReceiveProps(nextProps) {

    }
    shouldComponentUpdate(nextProps, nextState) {

    }
    componentWillUpdate(nextProps,nextState) {

    }
    componentDidUpdate(prevProps, prevState) {

    }
  }
复制代码

如果组件发生自身更新那么次执行scu,cwu,render,cdu
shouldComponentUpdate是一个特别的方法,接受props和state让开发者判断其是否需要更新。
* 值得注意的是stateless组件没有生命周期方法,也意味着每次都会重新渲染。我们可以使用Recompose库的pure方法:const OptimizedComponent = pure(ExpensiveComponent)

整体流程图

  • ReactDOM 主要方法findDOMNode(获取真实dom元素)、render(渲染VirtualDom到dom)、unmountComponentAtNode卸载操作
  • React之外的dom操作
    React提供了时间绑定功能,单是仍然有一些特殊的情况需要自行绑定事件,例如Popup,React中使用DOM最多的还是极端DOM尺寸,例如:
function width(el) {
    const styles = el.ownerDocument.defaultView.getComputedStyle(el,null);
    const width = parseFloat(styles.width.indexOf('px') !== -1 ? styles.width : 0);
    
    const boxSizing = styles.boxSizing || 'content-box';
    if(boxSizing) {
        return width;
    }
    const borderLeftWidth = parseFloat(styles.borderLeftWidth);
    const borderRightWidth = parseFloat(styles.borderRightWidth);
    const paddingLeft = parseFloat(styles.paddingLeft);
    const paddingRight = parseFloat(styles.paddingRight);
    
    return width - borderRightWidth - borderLeftWidth - paddingLeft - paddingRight;
    }
}
复制代码

文末奉献一版手打tab组件 (Tabs、TabNav、TabContent)

//Tabs
class Tabs exntends Component {
    static propTypes = {
        className: PropTypes.string, //主节点上增加可选class
        classPrefix: PropTypes.string, //class前缀
        children: PropTypes.oneOfType([
            PropTypes.arrayOf(PropsTypes.node),
            PropTypes.node
        ]),
        // 默认激活索引,组件内更新
        defaultActiveIndex: PropTypes.number,
        // 默认激活索引,组件外更新
        activeIndex: PropTypes.number,
        // 切换时回调函数
        onChange: PropTypes.func,
    }
      static defaultProps = {
    classPrefix: 'tabs',
    onChange: () => {},
  };

  constructor(props) {
    super(props);

    // 对事件方法的绑定
    this.handleTabClick = this.handleTabClick.bind(this);

    const currProps = this.props;

    let activeIndex;
    // 初始化 activeIndex state
    if ('activeIndex' in currProps) {
      activeIndex = currProps.activeIndex;
    } else if ('defaultActiveIndex' in currProps) {
      activeIndex = currProps.defaultActiveIndex;
    }

    this.state = {
      activeIndex,
      prevIndex: activeIndex,
    };
  }


  componentWillReceiveProps(nextProps) {
    // 如果 props 传入 activeIndex,则直接更新
    if ('activeIndex' in nextProps) {
      this.setState({
        activeIndex: nextProps.activeIndex,
      });
    }
  }

  handleTabClick(activeIndex) {
    const prevIndex = this.state.activeIndex;

    // 如果当前 activeIndex 与传入的 activeIndex 不一致,
    // 并且 props 中存在 defaultActiveIndex 时,则更新
    if (this.state.activeIndex !== activeIndex &&
        'defaultActiveIndex' in this.props) {
      this.setState({
        activeIndex,
        prevIndex,
      });

      // 更新后执行回调函数,抛出当前索引和上一次索引
      this.props.onChange({ activeIndex, prevIndex });
    }
  }

  renderTabNav() {
    const { classPrefix, children } = this.props;

    return (
      <TabNav
        key="tabBar"
        classPrefix={classPrefix}
        onTabClick={this.handleTabClick}
        panels={children}
        activeIndex={this.state.activeIndex}
      />
    );
  }

  renderTabContent() {
    const { classPrefix, children } = this.props;

    return (
      <TabContent
        key="tabcontent"
        classPrefix={classPrefix}
        panels={children}
        activeIndex={this.state.activeIndex}
      />
    );
  }

  render() {
    const { className } = this.props;
    // classnames 用于合并 class
    const classes = classnames(className, 'ui-tabs');

    return (
      <div className={classes}>
        {this.renderTabNav()}
        {this.renderTabContent()}
      </div>
    );
  }
}
复制代码
//TabsNav
class TabNav extends Component {
  static propTypes = {
    classPrefix: React.PropTypes.string,
    panels: PropTypes.node,
    activeIndex: PropTypes.number,
  };

  getTabs() {
    const { panels, classPrefix, activeIndex } = this.props;

    return React.Children.map(panels, (child) => {
      if (!child) { return; }

      const order = parseInt(child.props.order, 10);

      // 利用 class 控制显示和隐藏
      let classes = classnames({
        [`${classPrefix}-tab`]: true,
        [`${classPrefix}-active`]: activeIndex === order,
        [`${classPrefix}-disabled`]: child.props.disabled,
      });

      let events = {};
      if (!child.props.disabled) {
        events = {
          onClick: this.props.onTabClick.bind(this, order),
        };
      }

      const ref = {};
      if (activeIndex === order) {
        ref.ref = 'activeTab';
      }

      return (
        <li
          role="tab"
          aria-disabled={child.props.disabled ? 'true' : 'false'}
          aria-selected={activeIndex === order? 'true' : 'false'}
          {...events}
          className={classes}
          key={order}
          {...ref}
        >
          {  child.props.tab}
        </li>
      );
    });
  }

  render() {
    const { classPrefix } = this.props;

    const rootClasses = classnames({
      [`${classPrefix}-bar`]: true,
    });

    const classes = classnames({
      [`${classPrefix}-nav`]: true,
    });

    return (
      <div className={rootClasses} role="tablist">
        <ul className={classes}>
          {this.getTabs()}
        </ul>
      </div>
    );
  }
}
复制代码
//TabContent
class TabContent extends Component {
  static propTypes = {
    classPrefix: React.PropTypes.string,
    panels: PropTypes.node,
    activeIndex: PropTypes.number,
  };

  getTabPanes() {
    const { classPrefix, activeIndex, panels } = this.props;

    return React.Children.map(panels, (child) => {
      if (!child) { return; }

      const order = parseInt(child.props.order, 10);
      const isActive = activeIndex === order;

      return React.cloneElement(child, {
        classPrefix,
        isActive,
        children: child.props.children,
        key: `tabpane-${order}`,
      });
    });
  }

  render() {
    const { classPrefix } = this.props;

    const classes = classnames({
      [`${classPrefix}-content`]: true,
    });

    return (
      <div className={classes}>
        {this.getTabPanes()}
      </div>
    );
  }
}
复制代码
// TabPane
class TabPane extends Component {
  static propTypes = {
    tab: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.node,
    ]).isRequired,
    order: PropTypes.string.isRequired,
    disable: PropTypes.bool,
    isActive: PropTypes.bool,
  };

  render() {
    const { classPrefix, className, isActive, children } = this.props;

    const classes = classnames({
      [className]: className,
      [`${classPrefix}-panel`]: true,
      [`${classPrefix}-active`]: isActive,
    });

    return (
      <div
        role="tabpanel"
        className={classes}
        aria-hidden={!isActive}>
        {children}
      </div>
    );
  }
}
复制代码

小结

随着章节的深入,我们会陆续介绍 React 高阶使用方法、背后的运行机制、处理数据的架构 Flux 与 Redux。相信从现在开始,你已经做好在 React 的海洋里遨游的准备了。

转载于:https://juejin.im/post/5adc042ff265da0b78681895

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值