React 初识

React

We built React to solve one problem: building large applications with data that changes over time.

  • 声明式的,用于构建用户界面的 JavaScript 库
  • 组合模型,using composition instead of inheritanc
  • 单向响应的数据流
  • JSX,语法糖,类型检查,执行速度快(尽可能减少与DOM直接操作的次数)

核心

  • 组件
  • 虚拟DOM:解决jQuery操作真实DOM慢的问题
  • 响应式UI

普通的现代构建管道通常包括

  • 包管理器(package manager):如npm或Yarn。它可以利用大量的第三方软件包生态系统,并轻松安装或更新它们
  • 打包工具(bundler):如webpack或Browserify。它允许编写模块化代码并将他们打包成为一个小包,以实现加载性能的优化,节省加载时间
  • 编译器(compiler):如Babel。它可以在编写现代JavaScript代码的同时兼容旧版本浏览器

常用库概览

react.js:React的核心库
react-dom.js:提供与DOM相关的操作功能
Browser.js:将JSX语法转为JavaScript语法(耗时)

元素

Elements are the smallest building blocks of React apps.

 

组件

Small and Isolated pieces of code,模版即组件,组件即HTML自定义标签,是包含了模板代码的一种特殊的HTML标签类型。

  • 函数式组件
  • 类组件

当组件第一次渲染到DOM时,在React中称为挂载(mounting);当组件产生的DOM被销毁时,在React中称为卸载(unmounting)。

所有React组件都必须是纯函数,并禁止修改其自身props 。在JSX回调中必须注意this的指向,提供3种方法:

  • 在构造函数中显式绑定:.bind(this)
  • 使用箭头函数:onClick={(e) => this.handleClick(e)}
  • 保持使用ES5风格:createReactClass

推荐第1种。箭头函数每次渲染时都创建一个不同的回调。多数情况下没问题,然而如果这个回调被作为prop(属性)传递给下级组件,这些组件可能需要额外的重复渲染。

setState

  • (可能是)异步执行
  • 构造函数是唯一可以初始化 this.state 的地方
// ok,当前值不依赖上一次的值
this.setState({name: 'Hello'});

// 提供callback方式1
this.setState((prevState, props) => { 
  return { ...prevState, name: props.name }; 
});
// 或
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));
// 或
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

// 提供callback方式2
this.setState({
  name: 'qwer'
  }, ()=>{
      console.log(this.state.name); //qwer
});

在状态更新、渲染完成后,会触发对回调函数的执行。有关信息参见:react - setState

状态提升(Lifting State Up)

state创建有2种:

  1. 当组件以class类创建,state可以是class的属性值,也可以在构造函数通过this.state赋值来创建
  2. 当组件以函数创建,state需通过函数的getIntialState函数的返回值来创建

在React中,共享state(状态)是通过将其移动到需要它的组件的最接近的共同祖先组件来实现,使React的state成为 “单一数据源原则” 。
在一个React应用中,对于任何可变的数据都应该循序“单一数据源”原则,依赖从上向下的数据流向。
对于UI中的错误,使用React开发者工具来检查props,向上遍历树,直到找到负责更新状态的组件,跟踪到bug的源头。
具体参见:React Developer Tools

将参数传递给事件处理程序

  • arrow functions:箭头函数方式,参数e作为React事件对象作为第二个参数进行显式传递
  • Function.prototype.bind:bind方式,事件对象以及更多的参数将会被隐式传递
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

对于第2种方法,方法定义的形式

deleteRow(id, event) {...}  

当需要从子组件中更新父组件的state时,要在父组件创建事件句柄 (handleChange) ,并作为prop (updateStateProp) 传递到子组件。

var Content = React.createClass({
  render: function() {
    return  <div>
                <button onClick = {this.props.updateStateProp}>点我</button>
                <h4>{this.props.myDataProp}</h4>
              </div>
  }
});
var HelloMessage = React.createClass({
  getInitialState: function() {
    return {value: 'Hello Runoob!'};
  },
  handleChange: function(event) {
    this.setState({value: '菜鸟教程'})
  },
  render: function() {
    var value = this.state.value;
    return <div>
              <Content myDataProp = {value} 
                 updateStateProp = {this.handleChange}></Content>
              </div>;
  }
});
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

综上,对于显式bind方式,通式如下

handleclick(要传的参数,event){...} 
onClick = {this.handleclick.bind(this,要传的参数)}

组件钩子函数生命周期

。。。

高阶组件

高阶组件是一个函数,接受一个组件并返回一个新的组件

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 高阶组件既不会修改输入组件,也不会通过继承来复制行为。
  • 通过包裹的形式,高阶组件将原先的组件组合在容器组件中。
  • 高阶组件是纯函数,没有副作用。
  • 高阶组件最好是通过将输入组件包裹在容器组件的方式来使用组合。

错误边界

Error Boundaries 是React组件,它可以在子组件树的任何位置捕获JavaScript错误、记录这些错误,并显示一个备用UI,而不是使整个组件树崩溃。但是,仅可以捕获其子组件的错误,无法捕获其自身的错误。对以下情况无能为力:

  • 事件处理:事件处理器内部的错误,采用try{}catch(){}捕获即可
  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
  • 服务端渲染
  • 错误边界自身抛出来的错误(而不是其子组件)

钩子函数:componentDidCatch(error, info)
类似JS的catch(){}方法,如果一个错误边界无法渲染错误信息,则错误会向上冒泡至最接近的错误边界。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}   

ref属性

用来绑定到render()输出的任何组件上,允许引用render()返回的相应的支撑实例(Backing Instance)。

简言之,用于从组件获取真实的DOM结点。

  • 处理focus、文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方DOM库
  • 访问<input type="file">表单要提交处理的文件

组件并不是真实的DOM节点,而是存在于内存之中的一种数据结构,叫做虚拟DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的DOM 。根据 React 的设计,所有的DOM变动,都先在虚拟DOM上发生,然后再将实际发生变动的部分,反映在真实DOM上,这种算法叫做 DOM diff,可以极大提高网页的性能表现。

场景:获取组件new出来的实例,绕过父子组件通信的约束,直接操作组件new出来的实例

  • 通过 ref 属性获取真实的React元素实例
  • 通过 ReactDOM.findDOMNode(组件真实实例) 返回真实的DOM结构

通过ref属性获取到React组件new出来的真实实例,前置条件:

  • 自定义组件
  • 通过class来定义

真实DOM结点结构 = ReactDOM.findDOMNode(组件真实实例)

this.props.children属性

表示组件的所有子结点,其值有三种可能:

  • undefined:当前组件没有子节点
  • object:有且仅有有一个子节点
  • array:有多个子节点

React提供工具方法:React.Children,智能处理(容错) this.props.children。

React.Children.map(children, function[(thisArg)])
React.Children.forEach(children, function[(thisArg)])
React.Children.count(children)
React.Children.only(children)
React.Children.toArray(children)

条件渲染组件

To do this return null instead of its render output:从组件的render方法返回null不会影响组件生命周期方法的触发。

function WarningBanner(props) {
  if (!props.warnsInfoMsg) {
    return null;
  }

  return (
    <div className="warning">
      Warning! {this.props.warnsInfoMsg}
    </div>
  );
}

模块加载机制

CommonJS规范

  • fs:文件系统模块,负责读写文件,支持stream和pipe(自动流式读写)
  • http:web服务器模块,提供request和response对象分别封装http请求和响应  
  • cypto:加密解密模块,哈希算法,数字证书
// 模块对外提供变量或函数方法
module.exports = {
    key: value
    ...
};
// 引入模块
const module = require('./Module.js');

context

上下文,提供通过组件树传递数据的方法,在组件间共享数据,避免通过中间元素传递 props。

  • 使用props参数传递方式
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}
  • 使用context:Stick to cases where the same data needs to be accessed in many components at multiple levels.
// Context lets us pass a value deep into the component tree without explicitly threading it through every component.

// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to pass the theme down explicitly anymore.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  // Use a Consumer to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

其中,ThemeContext 是 {Provider, Consumer} 对象

const {Provider, Consumer} = React.createContext(defaultValue);

<Provider value={/* some value */}>

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>  

仔细体会,该方式类似发布订阅模式,provider生产数据,Consumer消费数据。 

具体参见:Context - React

编程思想

页面设计流程

  1. 组件拆解:自下而上,由内而外,单一职责原则
  2. 组件组合:各组件组合成静态页面框架
  3. 确定UI状态(state)的最小但完整集合表示
  4. 确定state位置:状态提升,公共父级组件
  5. 组件通信交互:回调函数(反向数据流)
    • 父组件将方法名作为参数传递给子组件
    • 在子组件上触发的事件调用父组件的方法以更新父组件的状态

注:不要在子组件上直接调用父组件方法。子组件应该封装一个事件句柄,在句柄内调用回调函数。

关于确定 UI state

  • 是否通过props(属性)从父级传入? 若是,它可能不是state(状态)。
  • 是否永远不会发生变化? 若是,它可能不是state(状态)。
  • 是否可以由组件中其他的state(状态)或props(属性)计算得出?若是,则它不是state(状态)。

具体参见:React 编程思想

学习了下React 核心开发者出品的 React 设计思想 ,简单总结如下:

  • 变换(Transformation):纯函数

  • 抽象(Abstraction):函数/组件调用

  • 组合(Composition):组合思想,而非继承

  • 状态(State):不可变性,setState()

  • Memoization:记忆缓存

  • 列表(Lists)

  • 连续性(Continuations)

  • 代数效应(Algebraic Effects):context

经验避坑

关于React注释

  • 在标签外的的注释不能使用花括号
  • 在标签内部的注释需要花括号
ReactDOM.render(
    /*注释 */
    <h1>xxxx {/*注释*/}</h1>,
    document.getElementById('root')
);

关于Html与React

  • 在React的.js文件中,标签中属性和事件必须驼峰格式
  • 为组件添加属性时,原生Html中的关键字class和for必须写成className和htmlFor 

环境判断

根据浏览器和Node环境提供的全局变量名称来判断当前环境:

if (typeof(window) === 'undefined') {
    console.log('node.js');
} else {
    console.log('browser');
}

关于规范

  • 添加属性Object.assign() 
  • 扩展运算符(...)拷贝数组  
  • Array.from():将类数组对象转为数组  
  • 所有配置项都应该集中在1个对象中,作为最后1个参数,布尔值不可以直接作为参数。  
  • 使用帕斯卡式命名构造函数或类 

具体参见:Airbnb React/JSX Style Guide;  

参考

React 官网 - EngReact 中文文档

React 入门实例教程 -阮一峰

posted @ 2018-06-18 19:18  万箭穿心,习惯就好。  阅读(316)  评论(0编辑  收藏  举报