React

React-用于构建UI界面的前端框架

2017-06-06 | 阅读

React

官方文档

中文文档

React起源于Facebook内部项目,用于架设Instagram网站,于2013年开源。 由于React设计思想极其独特,属于革命性创新,所有受到很多人的关注。

React的革命性创新有两点, 一个是Virtual DOM ,一个是JSX

Virtual DOM

虚拟DOM的核心思想是: 对复杂的DOM结构,提供一个方便的工具,进行最小化的DOM操作。

DOM是很慢的,其元素非常庞大,而JavaSript很快。所以如果能在DOM上再加一层,来优化DOM操作,就可以让整体性能提升很多。

Virtual DOM的操作不会马上产生真实效果,React 会在事件循环结尾时,计算最小的diff,以最小的步骤将diff作用到真实地DOM上。Virtual DOM的主要操作如下图所示 :

Matt-Esch的实现

JSX

JSX语法如下:

const element = <h1>Hello, world!</h1>;

JSX的语法可以让我们在JS中使用标签语法来构建UI,同时也可以在JSX添加JavaScript表达式, 如 :

const element = (
  <h1>
    Hello, {userName}!
  </h1>
);

在嵌入JavaScript表达式时 ,我们需要用 花括号 。 而在将JSX嵌入到JS代码中时,推荐使用 括号 来包裹(避免分号自动插入的问题, 即如果是单行的,可以不用括号,但是多行的,必须用括号)。

JSX是一个表达式,最后生成的是一个JavaScript对象,从本质来说, JSX只是React.createElement(component,props,...children)函数的语法糖,可以使用在线Babel编译器来查看JSX转换的结果。

JSX 接近与 JavaScript, 所以会用驼峰式(camelCase)命名属性,而不是HTML属性名称。

用户定义的组件必须以大写字母开头,React认为小写开头的组件为内置组件。

运行时选择类型

不能使用一个普通表达式来作为React元素类型, 如:

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 错误!JSX 类型不能是表达式
  return <components[props.storyType] story={props.story} />;
}

但是可以将表达式的结果赋值给一个大写字母开头的变量:

function Story(props) {
  // 正确!JSX 类型可以是一个以大写字母开头的变量.
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

属性扩展

通过...扩展符号传入整个props对象,以下两种方式是相同的:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

Children

JSX标签中的内容,被传递一个特殊的props: props.children。以下为几种children类型 :

字符串字面量

可以在标签中放入一个字符串:

<MyComponent>Hello world!</MyComponent>

JSX会删除每行开头和结尾的空格,并且也会删除空。邻接标签的空行也会被移除,字符串之间的空格会被压缩成一个空格。

JSX组件

<div>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>

可以混合不同类型的children

JavaScript 表达式

通过{}包裹 :

<MyComponent>{'foo'}</MyComponent>

Funcations

通常情况下, 嵌入到JSXJavaScript表达式,会被认为是一个字符串、React元素,或者这些元素的一个列表。 但是props.children与其他属性一样,是一个属性,可以传入任何数据。只要在渲染之前,组件可以将其转换为React能够处理的内容即可。

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

Booleans,Null和Undefined被忽略

在条件渲染时很有用。 如:

<div>
  {showHeader && <Header />}
  <Content />
</div>

组件

组件使UI划分为一个一个独立可复用的小部件,并可以对每个部件进行单独的设计。 组件是UI组件,组件的划分时,要结合状态,使组件能够处理自己的事件以及修改自己的状态,通过小组件来组成大组件,而组件之间互相独立。组件划分示例 :

组件的特征:

  • Composeable : 可组合 , 一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。
  • Reusable : 可重用 ,每个组件都具有独立功能,可以被使用在多个UI场景。
  • Maintainable : 可维护,每个组件只包含自身的逻辑,易于理解和维护。

一个组件代码的示例:

class HelloWorld extends React.Component{
  render() {
    return (
      <p>
        Hello, <input type="text" placeholder="Your name here" />!
        It is {this.props.date.toTimeString()}
      </p>
    );
  }
};

props

属性,是一组对外接口。 组件很少需要对外公开方法,唯一交互的方式就是 props。 这使得组件像函数一样,给定属性,组件给定一个界面输出。React 通过唯一的props接口避免了逻辑复杂性,让开发测试更加简单。

props是只读的,禁止修改自己的props

state

组件可以看做一个状态机,一开始有一个初始状态,然后用户操作导致状态变化,从而触发重新渲染UI。

state 一般在构造函数中初始化 :

constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

不能直接修改状态,而是通过setState函数:

this.setState({date: new Date()});

状态更新是异步,React为优化性能,将多个setState调用合并为一次更新。

setState会将结果合并到当前状态中,如当前状态为 {posts:[],comments:[]} , 调用 :

this.setState({comments:[]})

只会修改当前状态中的comments ,不会修改其他状态。

由于状态更新是异步,所以不能依赖他们的值来计算下一个状态 ,如下代码就可能导致计时器更新失败 :

this.setState({
  counter: this.state.counter + this.props.increment,
});

该函数调用时,可能以及触发了一次setState而由于异步更新,导致当前状态依旧是旧的。为解决这种问题,我们使用setState的第二种格式:

this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

setState可以接受一个以当前state和props为参数的函数,该函数返回值用于修改状态。

生命周期

组件拥有生命周期,以下方法会在生命周期中调用 :

注 : 如果使用类继承声明的组件,不会拥有getDefaultPropsgetInitialState这两个初始化的回调, 属性通过声明static defaultProps,而状态放在构造函数中初始化。

事件处理

React元素的事件处理与DOM元素事件处理相似,区别在于:

  • React 事件使用驼峰命名,而不是全部小写。
  • 通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串。

还有一个区别,在React中不能通过返回false来阻止默认行为,必须明确调用preventDefault ,如 :

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

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

JSX中设置事件处理函数时,记得使用bind(this),以绑定this

单向数据流

数据向下流动 :无论作为父组件还是子组件,它都无法获悉一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。所以状态是本地状态,它不能被当前组件以外的任何组件访问。

状态可以向下流动,即将当前组件的状态作为子组件的属性。任何状态始终由某个特定组件所有,并且只能影响树下方的组件。

当子组件的状态变化要修改父组件的显示时,一般通过回调的形式来实现。