React 详解

React 官方文档 :  https://reactjs.org/
React Native 智能提示 : 可参考  https://github.com/virtoolswebplayer/ReactNative-LiveTemplate
React 调试工具 : 可参考  https://github.com/facebook/react-devtools

ReactJS 是 Facebook 开源的一个 js库可用于动态构建用户界面

React 的特点
1> Declarative (声明式编码)
2> Component-Based (组件化编码)
3> Learn Once, Write Anywhere (支持客户端与服务器渲染)
4> 高效
5> 单向数据流

React高效的原因
1> 虚拟(virtual) DOM, 不总是直接操作DOM
2> 高效的 DOM Diff 算法, 最小化页面重绘

使用 react 经常会遇到几个组件需要共用状态数据的情况,最好将这部分共享的状态提升至最近的父组件当中进行管理

创建 React App
react提供了一个专门用于创建react项目的脚手架库 : create-react-app
使用 create-react-app 创建
$ npm install -g create-react-app     ## 全局安装 create-react-app
$ create-react-app my-app     ## 在当前目录中,创建 my-app 工程
$ cd my-app
$ npm start     ## 启动工程

在 build/ 文件夹内创建一个生产版本的应用
$ npm run build


虚拟 DOM
React 提供了一些 API 来创建一种 "特别" 的一般 js对象
var element = React.createElement('h1', {id:'myTitle'}, 'hello');
上面创建的就是一个简单的虚拟 DOM对象,虚拟DOM对象 最终都会被 React 转换为真实的DOM,编码时基本只需要操作 react的虚拟DOM相关数据,react会转换为真实 DOM变化而更新界面

当元素类型以小写字母开头时,它表示一个内置的组件,如
,并将字符串 'div' 或 'span' 传递给 React.createElement。 以大写字母开头的类型,如 编译为 React.createElement(Foo),并它正对应于在 JavaScript 文件中定义或导入的组件
注 : 建议用大写开头命名组件


JSX
JSX 全称 JavaScript XML,这是 react 定义的一种类似于 XML 的 JS扩展语法 : XML+JS
JSX 可用来创建 react虚拟 DOM(元素)对象,如下 :
var ele =

Hello JSX!

;
注 : 1> 它不是字符串, 也不是HTML/XML标签
        2> 它最终产生的就是一个JS对象
       3>  书写 JSX 的时候一般都会带上换行和缩进,这样可以增强代码的可读性,与此同时推荐在 JSX 代码的外面扩上一个小括号
       4> JSX 标签是闭合式,因此一定闭合
       5> 因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称,如 class 变成 className 而 tabindex 则对应着 tabIndex
标签名任意 : HTML标签或其它标签
标签属性任意 : HTML标签属性或其它

标签的 class属性必须改为 className属性
标签的 style属性值必须为 : {{}}

JSX 属性
使用引号来定义以字符串为值的属性 :
const element =
;
也可以使用大括号来定义以 JavaScript 表达式为值的属性 :
const element = ;
注 : 使用大括号包裹 JavaScript 表达式时就不要再到外面套引号,套引号后 JSX 会将引号当中的内容识别为字符串而不是表达式

基本语法规则
遇到以 <开头的代码, 以标签的语法解析 : html同名标签转换为 html同名元素,其它标签需要特别解析
遇到以 { 开头的代码,以JS的语法解析 : 标签中的 js代码必须用 {} 包含

babel.js 的作用
1> 浏览器的 js引擎是不能直接解析JSX语法代码的, 需要babel转译为纯JS的代码才能运行
2> 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

渲染虚拟DOM(元素)
语法 : ReactDOM.render(virtualDOM, containerDOM) : 参数一是纯js或jsx创建的虚拟dom对象,参数二是用来包含虚拟DOM元素的真实dom元素对象 (一般是一个div)
作用 : 将虚拟 DOM元素渲染到真实容器DOM中显示

元素是构成 React 应用的最小单位,用来描述在屏幕上看到的内容

创建虚拟DOM的 2种方式
1> 纯JS (一般不用) : React.createElement('h1', {id:'myTitle'}, title)
2> JSX :

{title}


将元素渲染到 DOM 中
首先在一个 HTML 页面中添加一个 id="root" 的
:
在此 div 中的所有内容都将由 React DOM 来管理,所以将其称之为 “根” DOM 节点,一般只会定义一个根节点
将 元素传入一个名为 ReactDOM.render() 的方法来将其渲染到页面上 :
const element =

Hello, world

;
ReactDOM.render(
  element,
  document.getElementById('root')
);

ReactDOM.render()渲染组件标签的基本流程
1> React内部会创建组件实例对象
2> 得到包含的虚拟DOM并解析为真实DOM
3> 插入到指定的页面元素内部

React 只会更新必要的部分
React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分

可以将字符串常量作为属性值传递,下面这两个 JSX 表达式是等价的 :


当传递一个字符串常量时,它不会对其进行 HTML 转义,所以下面两个 JSX 表达式是相同的 :


没有给属性传值,它默认为 true,因此下面两个 JSX 是等价的 :

注 : 一般情况下,不建议这样使用,因为它会与 ES6 对象简洁表示法 混淆,比如 {foo} 是 {foo: foo} 的简写,而不是 {foo: true}。这里能这样用,是因为它符合 HTML 的做法

可以使用 ... 作为扩展操作符来传递整个属性对象,下面两个组件是等效的 :
function App1() {
  return ;
}

function App2() {
  const name = {firstName: 'Ben', lastName: 'Hector'};
  return ;
}

false、null、undefined 和 true 都是有效的子代,但不会直接被渲染,下面的表达式是等价的 :


{false}

{null}

{undefined}

{true}

想让类似 false、true、null 或 undefined 出现在输出中,必须先转换成字符串 :
 My JavaScript variable is {String(myVariable)}. 


组件
组件的2个基本概念
1> 组件类(工厂函数/构造函数/类),如 : MyCompoent
2> 组件标签,如 : 

React Component 有 3 种定义方式,分别是 React.createClass, class 和 Stateless Functional Component。推荐使用 class 和 Stateless Functional Component (无状态函数组件),这是一个函数 不是 Object,没有 this 作用域,是一个纯函数,如下 App Component :
//  方式1 :  工厂(无状态)函数(最简洁, 推荐使用)
function App(props) {
    function handleClick() {
        props.dispatch({ type: 'app/create' });
    }
    return
${props.name}
}

//  方式2 : ES6类语法(复杂组件, 推荐使用)
class App extends React.Component {
    handleClick() {
        this.props.dispatch({ type: 'app/create' });
    }
    render() {
        return
${this.props.name}
    }
}

//方式3 : ES5老语法(不推荐使用)
let App = React.createClass({
    handleClick() {
        this.props.dispatch({ type: 'app/create' });
    }
    render() {
        return
${this.props.name}
    }
});
注 : 1> 返回的组件类必须首字母大写
        2> 虚拟 DOM元素 必须只有一个根元素
        3> 虚拟 DOM元素必须有结束标签,像是 也必须有
       4> 组件的返回值只能有一个根元素

ReactDOM.render() 渲染组件标签的基本流程 :
1> React 内部会创建组件实例对象
2> 得到包含的虚拟DOM并解析为真实DOM
3> 插入到指定的页面元素内部

受控组件 : React 是一个单向数据流,但可以自定义双向数据流组件 (受控组件)

建议使用组合而不是继承来复用组件之间的代码

使用非受控组件时,通常希望 React 可以为其指定初始值,但不再控制后续更新。要解决这个问题,可以指定一个 defaultValue 属性而不是 value :
render() {
  return (
    
      
        Name:
        
          defaultValue="Bob"
          type="text"
          ref={(input) => this.input = input} />
      
      
    
  );
}

智能组件 : 在日常开发中,也简称“页面”。为何说它“智能”,因为它只会做一些很聪明的事儿,脏活累活都不干。它只对数据负责,只需要获取了数据、定义好数据操作的相关函数,然后将这些数据、函数直接传递给具体实现的组件即可

木偶组件 : 这里“木偶”一词用的特别形象,它总是被人拿线牵着。它从智能组件 (或页面) 那里接受到数据、函数,然后就开始做一些展示工作,它的工作就是把拿到的数据展示给用户,函数操作开放给用户。至于数据内容是什么,函数操作是什么,它不关心


props
组件3大属性之一 : props属性
每个组件对象都会有 props(properties的简写)属性,组件标签的所有属性都保存在 props 中,可通过 this.props.propertyName 来读取某个属性值

props属性可以从组件外向组件内传递数据(只读),无论是使用函数或是类来声明一个组件它决不能修改它自己的 props,因此所有的React组件必须像纯函数那样使用它们的 props

扩展属性 : 将对象的所有属性通过 props传递

对 props 中的属性值进行类型限制和必要性限制 : 使用 PropTypes 进行类型检测
$ npm install --save prop-types

注 : React.PropTypes 自 React v15.5 起已弃用,使用 prop-types 库代替
import PropTypes from 'prop-types';

//  Person.js
class Person extends React.Component {
   render() {
        return (
            
             
  • name :  {this.props.name}
  •              
  • age : {this.props.age}
  •            
            )
        };
    }

    Person.propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number
    }
    PropTypes 返回的是一系列验证函数,用于确保接收的数据类似是否是有效的,如上代码 PropTypes.string.isRequire 检测 name是否为字符串并且是必填的,PropTypes.number 检测 age 是否为数字且并非必须,当出现异常时会在控制台显示警告信息,出于性能原因仅在开发模式下检查 propTypes

    下面示例提供不同验证函数
    import PropTypes from 'prop-types';

    MyComponent.propTypes = {
      // 可以定义一个js原始类型的prop 默认请情况下,这是都是可选的
      optionalArray: PropTypes.array,
      optionalBool: PropTypes.bool,
      optionalFunc: PropTypes.func,
      optionalNumber: PropTypes.number,
      optionalObject: PropTypes.object,
      optionalString: PropTypes.string,
      optionalSymbol: PropTypes.symbol,

      // 任何可以渲染的东西:数字,字符串,元素或数组( 或片段 )
      optionalNode: PropTypes.node,

      // React元素
      optionalElement: PropTypes.element,

      // 也可以声明prop是某个类的实例。 内部使用的是JS的instanceof运算符
      optionalMessage: PropTypes.instanceOf(Message),

      // 可以通过将它作为枚举来确保你的prop被限制到特定的值
      optionalEnum: PropTypes.oneOf(['News', 'Photos']),

      // 可以是许多类型之一的对象
      optionalUnion: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number,
        PropTypes.instanceOf(Message)
      ]),

      // 某种类型的数组
      optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

      // 具有某种类型的属性值的对象
      optionalObjectOf: PropTypes.objectOf(PropTypes.number),

      // 采取特定样式的对象
      optionalObjectWithShape: PropTypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number
      }),

      // 可以用`isRequired`来连接到上面的任何一个类型,以确保如果没有提供props的话会显示一个警告
      requiredFunc: PropTypes.func.isRequired,

      // 任何数据类型
      requiredAny: PropTypes.any.isRequired,

      // 还可以指定自定义类型检查器。 如果检查失败,它应该返回一个Error对象。 不要`console.warn`或throw,因为这不会在`oneOfType`内工作
      customProp: function(props, propName, componentName) {
        if (!/matchme/.test(props[propName])) {
          return new Error(
            'Invalid prop `' + propName + '` supplied to' + ' `' + componentName + '`. Validation failed.'
          );
        }
      },

      // 还可以为`arrayOf`和`objectOf`提供自定义类型检查器。 如果检查失败,它应该返回一个Error对象
      // 检查器将为数组或对象中的每个键调用验证函数
      // 检查器有两个参数,第一个参数是数组或对象本身,第二个是当前项的键
      customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
        if (!/matchme/.test(propValue[key])) {
          return new Error(
            'Invalid prop `' + propFullName + '` supplied to' +  ' `' + componentName + '`. Validation failed.'
          );
        }
      })
    };

    使用 PropTypes.element 可以指定只传递一个子代
    import PropTypes from 'prop-types';

    class MyComponent extends React.Component {
      render() {
        // This must be exactly one element or it will warn.
        const children = this.props.children;
        return (
          
            {children}
          
        );
      }
    }

    MyComponent.propTypes = {
      children: PropTypes.element.isRequired
    };

    配置 defaultProps 为 props定义默认值 : 
    class Greeting extends React.Component {
        render() {
            return

    hello {this.props.name}

    ;
        };
    }

    // 如果name没有传值,则会将name设置为默认的 " 张三 "
    Greeting.defaultProps = {
        name: ' 张三 '
    }

    // 会渲染处

    hi 张三

    ReactDOM.render(
        ,
        document.getElementById('root')
    )
    注 : 类型检查发生在 defaultProps 赋值之后,所以类型检查也会应用在 defaultProps 上面

    children : 表示组件的所有子节点,也可以说是 在包含开始和结束标签的 JSX 表达式
    function FancyBorder(props) {
      return (
        
          {props.children}
        
      );
    }


    state
    组件被称为 "状态机",通过更新组件的状态值来更新对应的页面显示 (重新渲染)

    通常在构造函数中初始化状态
    constructor (props) {
      super(props);
      this.state = {
        stateProp1 : value1,
        stateProp2 : value2
      };
    }

    读取某个状态值 : this.state.statePropertyName

    使用 this.setState() 方法来更新组件局部状态
    1> 不要直接更新状态 : 使用 setState() 该方法将会调用 render() 方法来重绘页面
    2> 状态更新可能是异步的 : React 可以将多个 setState() 调用合并成一个调用来提高性能。因为 this.props 和 this.state 可能是异步更新的,不能依靠它们的值来计算下一个状态
    // 错误
    this.setState({
      counter: this.state.counter + this.props.increment,
    });

    // 正确 :  使用第二种形式的 setState() 来接受一个函数而不是一个对象,该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));


    refs
    用于操作指定的 ref属性的 dom元素对象 (表单标签居多)
    { username = input; }}>

    对于如下写法访问 DOM 节点不推荐,因为 String 类型的 refs 存在问题,它已过时并可能会在未来的版本是移除,如果目前还在使用 this.refs.textInput 这种方式访问 refs ,建议用回调函数的方式代替
    this.refs.username

    组件内的标签都可以定义 ref属性来标识自己,在组件中可以通过 this.refs.refName 来得到对应的真实DOM对象

    不能在函数式组件上使用 ref 属性,因为它们没有实例 :
    function MyFunctionalComponent() {
      return ;
    }

    class Parent extends React.Component {
      render() {
        // 这里 `ref` 无效!
        return (
          
            ref={(input) => { this.textInput = input; }} />
        );
      }
    }

    可以在函数式组件内部使用 ref,只要它指向一个 DOM 元素或者 class 组件 :
    function CustomTextInput(props) {
      return (
        
          
            type="text"
            ref={(input) => { textInput = input; }} />
        
      );  
    }

    refs 的使用场景 :
    1> 处理焦点、文本选择或媒体控制
    class AutoFocusTextInput extends React.Component {
      componentDidMount() {
        this.textInput.focus();
      }

      render() {
        return (
          
            ref={(input) => { this.textInput = input; }} />
        );
      }
    }
    2> 触发强制动画
    3> 集成第三方 DOM 库
    如果可以通过声明式实现,则尽量避免使用 refs

    可能首先会想到在应用程序中使用 refs 来更新组件,如果是这种情况,请花一点时间,更多的关注在组件层中使用 state,在组件层中,通常较高级别的 state 更为清晰


    key
    当现实列表时在控制台可能会出现 a key should be provided for list items 警告,意思是当创建一个元素时必须包括一个特殊的key属性,key属性能树的转换变得高效
    注 : 如果列表可以重新排序,不建议使用索引来进行排序, 使用索引来进行排序 会导致渲染变得很慢,因为 当数组中的数据发生变化时 React 比较更新前后的元素 key 值, 如果相同则更新,如果不同则销毁之前的,重新创建一个元素

    元素的 key只有在它和它的兄弟节点对比时才有意义。比方说,如果你提取出一个 ListItem组件,应该把 key保存在数组中的这个元素上,而不是放在 ListItem组件中的
  • 元素上

  • 数组元素中使用的key在其兄弟之间应该是独一无二的。然而,不需要是全局唯一的。当生成两个不同的数组时,可以使用相同的键
    注 : key 会作为给 React 的提示,但不会传递给你的组件


    生命周期
    组件的三个生命周期状态
    Mount : 插入真实 DOM
    Update : 被重新渲染
    Unmount : 被移出真实 DOM

    React 详解_第1张图片
    初始化是运行的是左边的 Initial render 过程,当父组件修改子组件的属性值是运行的是右边的 父组件render 过程

    React 为每个状态都提供了两种勾子(hook)函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用
    componentWillMount()
    componentDidMount() : 已插入真实DOM, 在render之后才会执行,会在第一次页面绘制成功后被调用,适合放置一些初始化方法 如 获取数据发起 ajax请求、添加定时器、开启监听等
    componentWillUpdate(object nextProps, object nextState)
    componentDidUpdate(object prevProps, object prevState)
    componentWillUnmount() : 页面被卸载时将会调用这个方法,可用于释放资源

    render() : 页面重绘都会调用 render() 方法,这个方法会经常被调用,当进行隐藏组件时让 render 返回 null 即可而不是渲染结果
    注 : 组件的 render 方法返回 null 并不会影响该组件生命周期方法的回调

    生命周期流程 :
    1> 第一次初始化渲染显示 : render()
         constructor() : 创建对象初始化state
         componentWillMount() : 将要插入回调
         render() : 用于插入虚拟DOM回调
         componentDidMount() : 已经插入回调
    2> 每次更新state : this.setSate()
         componentWillUpdate() : 将要更新回调
         render() : 更新(重新渲染)
         componentDidUpdate() : 已经更新回调
    3> 删除组件
         ReactDOM.unmountComponentAtNode(document.getElementById('example')) : 移除组件
         componentWillUnmount() : 组件将要被移除回调


    事件处理
    React 元素的事件处理和 DOM元素的很相似,但有所不同 :
    1> React 事件绑定属性的命名采用驼峰式写法,而不是小写,就是说采用 onXxx属性指定组件的事件处理函数,需要注意大小写
    2> 如果采用 JSX 的语法,需要传入一个函数作为事件处理函数,而不是一个字符串 (DOM元素的写法)
    3> React 使用的是自定义 (合成)事件,而不是使用的 DOM事件
    4> React 中的事件是通过委托方式处理的 (委托给组件最外层的元素)
    5> React 中不使用 return false 的方式阻止默认行为,而是使用 e.preventDefault()
    // es5

    // es6
    function ActionLink() {
      function handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
      }

      return (
         Click me 
      );
    }

    通过 event.target 得到发生事件的 DOM元素对象
    handleFocus(event) {
      event.target; // 返回input对象
    }

    使用 ES6 class 语法来定义一个组件的时候,事件处理器会成为类的一个方法

    组件内置的方法中的 this为组件对象

    类的方法默认是不会绑定 this,如果直接通过 this 来获取方法将会得到 undefined,有三种方式绑定方法
    // 方法1
    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
        // React构造方法中的bind 将 handleClick函数 与这个 组件Component进行绑定以确保在这个处理函数中使用 this时可以时刻指向这一组件
        this.handleClick = this.handleClick.bind(this);
      }

      handleClick() {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }

      render() {
        return (
          
        );
      }
    }

    // 方法2
    class LoggingButton extends React.Component {
      handleClick = () => {
        console.log('this is:', this);
      }

      render() {
        return (
          
            Click me
          
        );
      }
    }

    // 方法3
    class LoggingButton extends React.Component {
      handleClick() {
        console.log('this is:', this);
      }

      render() {
        return (
          
            Click me
          
        );
      }
    }
    第三种方式会在每次组件被重新渲染时都会创建一个不同的回调函数,建议使用前两种方式


    表单