Chapter1

作者: vam笙 | 来源:发表于2019-03-28 10:29 被阅读0次

React一些特点

  1. 专注于视图层
  2. 业务框架(也可以理解为model层)可以根据业务场景自行选择。
  3. Virtual DOM提高性能。
  4. 便于和其他平台集成
    • 基于VD我们可以渲染出不同的原生控件
    • 即组件在输出的时候,究竟是输出成web DOM,还是安卓控件,还是IOS控件,都有平台本身决定。
    • learn once, write anywhere

JSX

概念

  • 就是将HTML语法直接加入到JavaScript中,然后再通过编译器转换为纯JS后有浏览器执行。

写法注意点:

  1. 定义标签时,只允许被一个标签包裹,即必须只有一个root节点

    • 错误写法

        <span>11</span>
        <span>22<span>
      
    • 正确

        <div>
           <span>111</span>
           <span>222<span>
        </div>
      
  2. 标签必须闭合,即单标签必须有 />

    • 错误

       <Com >
      
    • 正确

       <Com />
      
  3. DOM元素用小写字母开头,组件元素用大写字母开头

  4. 命名空间,解决命名冲突或对一类组件进行分类

     //namespace.js
     import React, { Component } from 'react';
    
     const com1 = () => {
       return(
          <div>
             NameSpace命名空间下的com1组件
         </div>
       )
     }
    
     const com2 = () => {
        return(
          <div>
             NameSpace命名空间下的com2组件
          </div>
        )
     }
    
     export const NameSpace = {com1, com2};
    
     // app.js
     import { NameSpace } from './namespace.js';
    
     render() {
         return() {
           <NameSpace.com1 />
           <NameSpace.com2 />
         }
     }
    
  5. 注释

  6. DOCTYPE(有些特殊,后续会说到)

元素属性

改变

  • class属性改为className
  • for属性改为htmlFor

写法

  • 自定义属性:

    1. 小驼峰写法

    2. 往DOM元素中传入自定义属性,是不会渲染的;但如果要使用HTML自定义属性,则需要加上data-前缀

      <div d=”xxx”>content</div>   // ×
      <div data-d=”xxx”>content</div>   //√     
      
  • boolean属性:默认为false

      /** 如果写<Checkbox checked={true} />  
          直接写为<Checkbox checked /> */
      <Checkbox checked={true} />
    
      /** 如果需要<Checkbox checked={false} />  
          那么直接写<Checkbox />即可,直接省略checked属性*/
      <Checkbox />
    

    同类型还有disabled, required, readOnly, selected等。

  • rest写法提升效率

      const data = { name: ‘foo’, value: ‘bar’ }; const Com = <Component {...data} />
    
  • 属性表达式:凡是用到js语法的,都需要在外面包一层{}

组件

Web Component规范

  • 这个规范是W3C用来统一web端组件定义的。它通过自定义组件的方式来统一组件,现在的三大框架也都在向各个规范靠近并逐渐趋于稳定。
  • 在该规范中,一共定义了以下四个方面。具体定义的内容我没有看。这里只是先做一个了解


    WebComponent包括的内容
  • 在平时写组件时,每个自定义的元素对外提供属性,方法,事件,而内部就专注于实现功能,以此来完成对组件的封装。这其实就是在遵循Web Component规范~

组件的创建

  • React组件的组成
  • 注意react组件与Web Component传达的思想一样,但具体实现不同。
  • 创建方法
    1. ES6 Classes

       import React, { Component } from 'react';
       import PropTypes from 'prop-types';
      
       class Com extends Component {
           constructor(props) {
               super(props);
           }
      
           render() {
               return(
                 <div>COM1</div>
               )
           }           
       }
      
    2. 无状态函数

      const Com = (props) => {
           return(
               <div>{ ...props }</div>
           )
       } 
      
    3. 区别:


      diffrence

数据流

单项数据流

  • 在React中数据是自顶向下单向流动的,即从父组件到子组件
  • 我们可以把所有的组件按照父子想象成一棵树,就像家族树一样,所有的数据就像基因一样,只能爸爸传给儿子,儿子可不能给粑粑。

states

  • setState
    1. 改变内部状态,即state值,执行完毕后最大的表现就是该组件会重新渲染。

    2. 该方法是异步方法,一个生命周期内所有的setState方法会合并操作。所以这里容易犯一个错误,即后一个state依赖前一个state,举个例子:

       this.state = {
           basic: 0,
           number: 1
       }
       componentDidMount() {
           this.setState({
               basic: 12,
               number: basic + 10
           })
       }
      

      我们本来想得到number=22,但其实得到了number=1,就是因为这个方法是一个异步方法,不是同步的,所以number并不会等basic复制完毕之后再复制,而是number和basic同时执行的。

props

  • 外部传进来的组件的属性(对比函数,就像入参一样)
  • 是react数据流主要的流动管道。
children属性
  • 是React内置的,代表子组件的集合。

  • // com.js
    render() {
      console.log(this.props.children);       //[{...}, {...}]
    
      return(
        <div>{this.props.children}</div>
      )
    }
    
     //App.js
    import Com from './com.js';
    
    <Com>
          <button>click</button>
          <input />
    </ Com>
    
  • 用函数式编程的方法来动态渲染数据

    React.Children.map(children, (child) =>{})
    
组件prop
  • 也可以将组件作为prop传入

    <TabPane tabNav={<span><i className="fa fa-home"/> &nbsp;home </span>} />
    
  • 父子组件通信

    • 父 -> 子:prop

    • 子 -> 父:callback回调

      // father.js
      import Child from './child.js';
      
      handleChange = (props) => {
        console.log('子组件返回的prop', props);       // 3
      }
      <Child data={2} onChange={this.handleChange} />
      
      //child.js
       this.state = {
          count: 0
       }
      <button onClick=() => { this.setState({ count: this.props.data +1 }); this.props.onChange(this.state.count) }>改变data</button>
      
propTypes
 import PropTypes from 'prop-types';

static propTypes = {
  name: PropTypes.string,
  value: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.func
  ]).isRequired
}

// 默认属性值
static defaultProps = {
  name: 'foo',
  value: 'bar'
}

React生命周期

  • 整体流程


    lifecycle.png
  • 代码示例加个生命周期讲解(每个生命周期的注释)

     class App extends Component {
       static propTypes = { 
           // ...
       };
       static defaultProps = {
           // ...
       }
       constructor(props) {
           super(props);
           this.state = {
             top: 1
           }
       }
       componentWillMount() {
         // 组件挂载之前,在render方法之前执行
         // 不用在这里setState,因为初始化渲染的时候组件只渲染一次(render),所以这是无意义的执行,初始化的state直接放到this.state里就好了
    
         console.log('---before mount---', this.state, this.props);  // top: 1, data: 2
         this.prevProps = this.props;
         this.prevState = this.state;
       }
       componentDidMount() {
         // 组件挂载之后,在render后执行
         // 如果在这里setState,那么就相当于组件光在初始化阶段就渲染了两次,并不好。
         // 但有些情况必须在这里setState,如计算组件的位置,宽高等这些需要组件渲染出来后才能得到的信息,就不得不让组件先渲染,然后更新信息,再次渲染。
         const top = this.refs['div'].getBoundingClientRect().top;
         this.setState({
           top
         })
         console.log('---after mount---', this.state.top, this.props.data); 
       }
       componentWillReceiveProps(nextProps) {
           // 在组件更新之前,即componentWillUpdate之前,会先执行该函数
          console.log('----传入prop之后,渲染之前----');
          console.log(nextProps);
         // 该函数可以作为props改变后,渲染之前改变state的机会。
          this.setState({
            top: nextProps.data
          })
       }
       shouldComponentUpdate(nextProps, nextState) {
         // 性能优化点,该函数接收更新的props和states,让我们可以作必要的条件判断处理,让组件在必要时更新,不必要时不更新
         // 返回true -> 更新 , 返回false -> 不更新
         let flag = false;
         if(this.prevProps.data !== nextProps.data || this.prevState.top !==               nextState.top) {
           flag = true;
         } else {
           flag = false;
         }
         this.prevProps = nextProps;
         this.prevState = nextState;
         console.log('是否需要更新---', flag)
    
         return flag;
       }
     componentWillUpdate(nextProps, nextState) {
       // 组件更新之前,入参为需要更新的值
       // 不可以在这里setState,不然会死循环,具体在Chapter3中说
       console.log('----before update----', nextProps, nextState);
     }
     componentDidUpdate(prevProps, prevState) {
       // 组件更新之后,入参为更新前的值
      // 如果在这里setState的话,必须在shouldComponentUpdate里用某一条件卡住,不然会死循环。
       console.log('----after update----');
       this.setState({
         top: 500
       })
      }
       render() {
         console.log('----render----')
         return (
           <div ref="div" className="div" style={{top: this.state.top}}>
             { this.state.top }
             hey, I'm testing life cycle in React!
           </div>
         )
       }
     }
    
  • 补充

    1. componentWillMount -> render -> componentDidMount: 组件的初始化阶段
    2. 如果组件自身state更新了,会依次执行componentWillReceiveProps ->shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
    3. DOM真正被添加到HTML中的方法是,componentDidMount和componentDidUpdate方法。(render方法的目的是输出Virtual DOM

React-DOM

  • 方法

    1. findDOMNode: 返回该组件实例对应的DOM节点,既然是DOM节点,所以只对已经挂载的组件有效(即只能在上述两个生命周期方法中调用)

    2. ReactDOM.render:只有在顶层组件中才会用到,因为要把react渲染的virtualDOM渲染到浏览器的真实DOM中。

       ReactDOM.render(<App />, document.getElementById('root'), callback);      // 组件装载完毕,callback调用,该方法返回APP组件的实例
      
    • 亮点: 在每次更新时,React不会把整个组件重新渲染一边,而会使用DOM diff算法做局部更新。
    1. unmountComponentAtNode: 进行卸载操作
    2. 不稳定方法 unstable_renderSubtreeIntoContainer
      • 顾名思义,不稳定的_将子组件渲染到容器中

      • 用法:

        ReactDom.unstable_renderSubtreeIntoContainer(
            parentComponent,
            nextElement,
            container,
            callback
        )
        eg: 
        // portal.js
        ReactDom.unstable_renderSubtreeIntoContainer(
            this,
            children,
            document.createElement('div'),
            this.props.onUpdate
         )
        //App.js
        <Portal>  
            <Modal>
                Modal content
            </Modal>
        </Portal>
        /** 就是在Portal组件中,将它的子组件插入新建的div中,Portal并不关心它的子组件是啥*/
        
      • 作用:

        • 从上面可以看出,Portal只是一个壳,它把modal的行为抽象了出来,即我们在Portal组件中通过该方法将内部的子组件放到了指定DOM节点上。而Portal本身并不对应任何一个DOM,只是作为一个中介控制modal的行为和把它渲染到另一DOM上。
      • 所以总结来说,该方法的作用就是

        1. 更新组件到传入的DOM节点上
        2. 完成了在组件内实现跨组件的DOM操作

refs

概念

  • 组件被调用是会创建一个该组件的实例,而refs(即reference,引用)指向这个实例。

形式

  1. 可以是回调函数,在组件被挂载后执行

    <input ref={(ref) => this.textInput = ref;} />
    
  2. 可以是字符串
    <input ref="input" />

  3. 不同组件类型得到的东西不一样

    • 如果ref在原生DOM组件上,则会得到DOM节点

        <buttton>...</button>
      
    • 如果ref在React组件上,则会得到该组件实例
      注意:如果组件为无状态组件,则给它上加ref的话,为undifined,无效。因为它有组件类实例化的过程,组件的挂载只是方法调用而已。

        Button {
            context: {},
            props: {},
            refs: {},
            children: {},
            ...
        }
      

与findDOMNode的区别

  1. 首先要明白实例和DOM节点不是一个东西。

  2. ref得到的是实例,就像上面的Button{...}一样,而如果改为findDOMNode方法,得到的是DOM,即就会是:<button>...</button>

  3. 不要把ref和findDOMNode混用,即

       const comRef = this.refs.com;
       const comDom = ReactDOM.findDOMNode(comRef);
       // do something with comDom
    
    • 一来,我们本来写这个组件是要具有封装性的,而如此在外面得到了该组件的DOM进行一系列操作,则打破了它的封装性。
    • 二来,我们打破了父子组件仅通过props通信的原则。

安全与性能

安全

  • 防止xss攻击
    1. react会将所有显示到DOM上的字符转义,防止XSS。所以当我们需要在JSX中用字符实体时,eg: &copy ; (©)版权标识,,那肯定不会正确显示,因为react把里面的字符实体给转换了。
    2. 解决:
      • 直接使用对应字符的UTF-8编码

      • 使用对应字符的unicode编码

      • 使用数组组装

          <div>{[‘cc’, <span>&copy;</span>, ‘2019’]}</div>
        
      • 直接插到html里

          <div>cc&copy;2019</div>
        
      • dangerouslySetInnerHTML属性
        名字起的很贴切,作用就是避免react转义字符

        <div dangerouslySetInnerHTML={__html: 'cc &copy; 2019'} />
        

性能

  • 在合适的情况下,尽可能使用无状态函数创建组件,因为它自始至终只保持了一个实例,减少了内存分配和一些无谓的检查,做到了内部优化。

  • 合理使用shouldComponentUpdate,减少不必要的组件渲染。
    - 无状态函数没有生命周期,所以没有该方法。
    - 但可以使用Recompose库的pure方法:

       /** 该方法将无状态组件转换成class语法加上PureRender后的组件*/
       const OptimizedCom = pure(ExpensiveCom);
    

注意点

  • 组件的最终目的是输出virtual DOM,即需要被渲染到界面的结构(后面章节会说到VD在js中的具体实现)。而做这件事的就是render,核心的渲染函数。
  • 在进行一些DOM API调用时,多想想自己为啥要操作DOM。因为DOM操作很费性能。

相关文章

网友评论

    本文标题:Chapter1

    本文链接:https://www.haomeiwen.com/subject/wpbfbqtx.html