【学习笔记】React

JSX

是什么?

JSX是JavaScript的语法扩展,用来描述用户界面javascript

使用规范

  1. JSX语句自己也是表达式,在JS代码中能够做为普通表达式使用
  2. JSX语句中引号包裹字符串,大括号包裹JS表达式
  3. JSX语言特性沿袭JS特性,所以属性名使用驼峰式规范
  4. JSX默认进行防注入攻击,渲染前会过滤全部传入值,而且将全部内容都转为字符串

扩展

模块

因为 JSX 编译后会调用 React.createElement 方法,因此在包含JSX的模块中必须先引入React 模块html

标签名

  1. 内置组件标签名以小写字母开头,如<div><span>。自定义组件标签名以大写字母开头
  2. 标签名可使用点表示法 <MyComponents.DatePicker color="blue" />
  3. 标签名不能为表达式java

    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} />;
    }

属性

  1. 若是你没有给属性传值,它默认为 true
  2. 若是已经有一个props对象,可使用扩展运算符传递整个属性对象

子代

  1. 子代能够是字符串常量,JSX,JS表达式,函数
  2. 布尔值、Null 和 Undefined 被忽略

本质

Babel转译器会将JSX转译为React.createElement方法,该方法首先会进行一些避免bug的检查,以后返回React元素(一个JavaScript对象)。JSX 只是为 React.createElement(component, props, ...children) 方法提供的语法糖。react

React元素渲染

渲染过程

React DOM 使 React 元素 和 浏览器 DOM 的数据内容保持一致npm

经过ReactDOM.render() 将React元素渲染到页面上json

更新元素

React元素是一个不可变的(immutable),元素被建立以后没法改变其内容和属性数组

若要更新,建立一个新的React元素,从新调用ReactDOM.render()渲染 浏览器

React DOM 首先会比较元素内容前后的不一样,而在渲染过程当中只会更新改变了的部分性能优化

组件

是什么?

独立的、可复用的部件服务器

接收任意的输入值(称之为“props”),并返回一个React元素。

定义方法

函数定义

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类定义

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Props

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

ReactDOM.render(
  <Welcome name="Sara" />,
  document.getElementById('root')
);

调用自定义组件,组件上的属性集合到props对象上传递给组件

全部的React组件必须像纯函数(输入值不会被改变,输入值相同总会输出相同的结果)那样使用它们的props。

PropTypes

类型检查

PropTypes 包含一整套验证器,可用于确保你接收的数据是有效的。

当你给属性传递了无效值时,JavsScript 控制台将会打印警告。出于性能缘由,propTypes 只在开发模式下进行检查。

注意: React.PropTypes 自 React v15.5 起已弃用。请使用 prop-types 库代替。

defaultProps

属性默认值

类型检查发生在 defaultProps 赋值以后,因此类型检查也会应用在 defaultProps 上面。

State & 生命周期

State

状态,组件内部维护

  • 构造函数是惟一可以初始化this.state的地方
  • this.propsthis.state的更新多是异步的,React 能够将多个setState() 调用合并成一个调用来提升性能。

    // Wrong
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    
    // Correct
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));
  • setState()是一个请求

    // 不依赖state计算时书写
    // callback在setState执行完成同时组件被重渲以后执行,等同于componentDidUpdate的做用
    this.setState({
        counter: 2
    }, [callback]);
    
    // 依赖state以前的状态
    this.setState((prevState) => {
      return {counter: prevState.quantity + 1};
    });
  • 单向数据流,经过state维护组件内部状态,经过props向子组件传递数据

生命周期

  • defaultProps, propTypes
  • constructor() - 构造函数是初始化状态的合适位置。
  • UNSAFE_componentWillMount() - 装配发生前调用,在这方法里同步地设置状态将不会触发重渲
  • render() - 渲染时调用
  • componentDidMount() - 组件装配完成时调用,初始化DOM节点,发送网络请求,事件订阅
  • UNSAFE_componentWillReceiveProps(nextProps) - 装配了的组件接收到新属性前调用,即便属性未有任何改变,也可能会调用该方法,请确保比较先后值
  • shouldComponentUpdate(nextProps, nextState) - 当接收到新属性或状态时在渲染前调用,该方法并不会在初始化渲染或当使用forceUpdate()时被调用
  • UNSAFE_componentWillUpdate() - 接收到新属性或状态时时调用,不能在这调用this.setState()
  • getDerivedStateFromProps(nextProps, prevState) - 组件实例化后和接受新属性时调用,调用this.setState() 一般不会触发,返回响应属性
  • getSnapshotBeforeUpdate(prevProps, prevState) - 在最新的渲染输出提交给DOM前将会当即调用,这一辈子命周期返回的任何值将会 做为参数被传递给componentDidUpdate()
  • componentDidUpdate(prevProps, prevState) - 更新发生后当即调用,不会在初始化渲染时调用。操做DOM,发送请求
  • componentWillUnmount() - 组件被卸载和销毁以前马上调用。清理定时器、请求、DOM节点、事件订阅

事件处理

this

类的方法默认不会绑定this,解决办法:

  • 在构造函数中使用bind绑定
  • 使用实验性的属性初始化器

    class LoggingButton extends React.Component {
      // This syntax ensures `this` is bound within handleClick.
      // Warning: this is *experimental* syntax.
      handleClick = () => {
        console.log('this is:', this);
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            Click me
          </button>
        );
      }
    }
  • 使用箭头函数定义回调函数

    class LoggingButton extends React.Component {
      handleClick() {
        console.log('this is:', this);
      }
    
      render() {
        // This syntax ensures `this` is bound within handleClick
        return (
          <button onClick={(e) => this.handleClick(e)}>
            Click me
          </button>
        );
      }
    }

    每次渲染时会建立一个不一样的回调函数,若是回调函数做为一个属性值传入低阶组件,会致使额外渲染

event

React封装的一个合成事件SyntheticEvent

事件处理函数返回false不会再阻止事件传播, 因此必须得手动触发e.stopPropagation()e.preventDefault() 方法。

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

传递参数

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'};
    }
    
    preventPop(name, e){    //事件对象e要放在最后
        e.preventDefault();
        alert(name);
    }
    
    render(){
        return (
            <div>
                <p>hello</p>
                {/* Pass params via bind() method. */}
                <a onClick={this.preventPop.bind(this, this.state.name)}>Click</a>
                <a onClick={(e) => {this.preventPop(this.state.name, e)}}>Click</a>
            </div>
        );
    }
}

条件渲染

在render函数中使用条件语句实现条件渲染,可使用变量来储存元素

在JSX中使用条件运算符组成表达式实现条件渲染

render 方法返回 null隐藏组件

列表

经过使用{}在JSX内构建一个元素集合

key

每一个列表元素须要有key属性

keys能够在DOM中的某些元素被增长或删除的时候帮助React识别哪些元素发生了变化

key会做为给React的提示,但不会传递给你的组件

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

表单

受控组件

经过onChange+setState控制状态改变,使React控制组件状态

<input type="text" value={this.state.value} onChange={this.handleChange} />
<textarea value={this.state.value} onChange={this.handleChange} />
<select value={this.state.value} onChange={this.handleChange}>
    <option value="grapefruit">Grapefruit</option>
    <option value="lime">Lime</option>
    <option value="coconut">Coconut</option>
    <option value="mango">Mango</option>
</select>

当你有处理多个受控的input元素时,你能够经过给每一个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择作什么。

非受控组件

经过ref获取获取DOM

在 React 的生命周期中,表单元素上的 value 属性将会覆盖 DOM 中的值

使用defaultValue指定初始值

状态提高

使用 react 常常会遇到几个组件须要共用状态数据的状况。

这种状况下,咱们最好将这部分共享的状态提高至他们最近的父组件当中进行管理。

组合 vs 继承

经过子代props.children或者自定义属性承载组件

使用组合实现包含关系,动态定义传入容器的子组件

使用组合实现特殊实例,替换继承的做用,好比说Prom

React理念

  • 组件按单一功能封装
  • 设定最小可变状态集,要点是 DRY:不要重复(Don’t Repeat Yourself)。找出应用程序的绝对最小表示并计算你所须要的其余任何请求。例如,若是你正在建立一个 TODO 列表,只要保存一个包含 TODO 事项的数组;不要为计数保留一个单独的状态变量。相反,当你想要渲染 TODO 计数时,只须要使用 TODO 数组的长度就能够了。

Refs & DOM

React v16.3

  1. 建立ref React.createRef()
  2. 关联ref,可使用直接关联也可以使用回调
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 建立 ref 存储 textInput DOM 元素
    this.textInput = React.createRef();
    this.buttonInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
    this.setTextInputRef = element => {
      this.buttonInput = element;
    };
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框得到焦点
    // 注意:经过 "current" 取得 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 咱们想把 <input> ref 关联到构造器里建立的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />

          
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
          ref={this.setTextInputRef}
        />
      </div>
    );
  }
}

ref 的更新会发生在componentDidMountcomponentDidUpdate 生命周期钩子以前。

性能优化

使用生产版本

开发模式下React会更大更慢,所以必定要用生产版本部署

使用 Chrome Performance 归档组件

  1. 在项目地址栏内添加查询字符串 ?react_perf(例如, http://localhost:3000/?react_perf)。
  2. 打开Chrome开发工具Performance 标签页点击Record.
  3. 执行你想要分析的动做。不要记录超过20s,否则Chrome可能会挂起。
  4. 中止记录。
  5. React事件将会被归类在 User Timing标签下。

避免重复渲染

使用SCU控制

避免数据突变

支持ES6的状况下,使用Object.assign/扩展运算符返回新数据,防止引用类型数据突变

使用Immutable.js保持数据不可变,下降出错率

Reconciliation

  1. 两个不一样类型的元素将产生不一样的树。
  2. 经过渲染器附带key属性,开发者能够示意哪些子元素多是稳定的。

Context

共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

不要仅仅为了不在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件须要访问相同数据的情景。

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

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

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

每当Provider的值发送改变时, 做为Provider后代的全部Consumers都会从新渲染。 从Provider到其后代的Consumers传播不受shouldComponentUpdate方法的约束,所以即便祖先组件退出更新时,后代Consumer也会被更新。

Fragments

聚合一个子元素列表,代替组件根元素使用

<></><React.Fragment/> 的语法糖。

<></> 语法不能接受键值或属性,要添加key属性须要使用<React.Fragment/>

Portals

Portals 提供了一种将子节点渲染到父组件之外的 DOM 节点的方式。

ReactDOM.createPortal(child, container)
// child 可渲染的React子元素
// container DOM元素

虽然DOM树中的位置平行,但React树中组件仍在根组件树中,树组件仍能捕获到事件

错误边界

是什么?

错误边界组件用于捕获其子组件树 JavaScript 异常,记录错误并展现一个回退的 UI

错误边界在渲染期间,生命周期方法内,以及整个组件树构造函数内捕获错误。

错误边界没法捕获事件处理函数内部的错误,事件处理函数内部的错误使用try...catch捕获。

错误边界没法捕获异步事件的错误,异步事件的错误在回调函数内部自行处理。

错误边界没法捕获服务器端渲染和错误边界自身抛出的错误。

自 React 16 开始,任何未被错误边界捕获的错误将会卸载整个 React 组件树。

组件内部

若是一个类组件定义了 componentDidCatch(error, info): 方法,则该组件是一个错误边界组件。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // error: 错误信息
    // info: 组件栈追踪
    this.setState({ hasError: true });
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

组件使用

错误边界的粒度彻底取决于你的应用。你能够将其包装在最顶层的路由组件并为用户展现一个 “发生异常(Something went wrong)“的错误信息,就像服务端框架一般处理崩溃同样。你也能够将单独的插件包装在错误边界内部以保护应用不受该组件崩溃的影响。

Web Components

Web Components是W3C规范的新功能,提供自定义元素封装功能。至关于在DOM的层面封装一个自定义元素,元素的展现和特性在Web Components内部实现。在React眼中,一个Web Component和一个普通元素无异。

React组件是React层面的元素单元,封装有UI、逻辑、数据响应。

高阶组件(HOC)

是什么?

对组件逻辑进行重用的一种抽象模式

高阶组件就是一个函数,且该函数接受一个组件做为参数,并返回一个新的组件

const EnhancedComponent = higherOrderComponent(WrappedComponent);

经过将原组件 包裹 在容器组件里面的方式来 组合 使用原组件。高阶组件就是一个没有反作用的纯函数。

需避免的问题

  • 请使用组合性高阶组件,避免使用更改性高阶组件

    在高阶组件内部修改(或以其它方式修改)原组件的原型属性,称为更改性高阶组件

    function logProps(InputComponent) {
      InputComponent.prototype.componentWillReceiveProps(nextProps) {
        console.log('Current props: ', this.props);
        console.log('Next props: ', nextProps);
      }
      // 咱们返回的原始组件实际上已经
      // 被修改了。
      return InputComponent;
    }
    
    // EnhancedComponent会记录下全部的props属性
    const EnhancedComponent = logProps(InputComponent);

    在高阶组件内部使用组合返回一个新的组件,称为组合型高阶组件

    function logProps(WrappedComponent) {
      return class extends React.Component {
        componentWillReceiveProps(nextProps) {
          console.log('Current props: ', this.props);
          console.log('Next props: ', nextProps);
        }
        render() {
          // 用容器组件组合包裹组件且不修改包裹组件,这才是正确的打开方式。
          return <WrappedComponent {...this.props} />;
        }
      }
    }
  • 高阶组件应该传递与它要实现的功能点无关的props属性

    render() {
      // 过滤掉与高阶函数功能相关的props属性,
      // 再也不传递
      const { extraProp, ...passThroughProps } = this.props;
    
      // 向包裹组件注入props属性,通常都是高阶组件的state状态
      // 或实例方法
      const injectedProp = someStateOrInstanceMethod;
    
      // 向包裹组件传递props属性
      return (
        <WrappedComponent
          injectedProp={injectedProp}
          {...passThroughProps}
        />
      );
    }
  • 最大化使用组合

    const ConnectedComment = connect(commentSelector, commentActions)(Comment);

    connect函数返回一个高阶组件

    使用compose代替高阶组件嵌套使用

    const enhance = compose(
      // 这些都是单参数的高阶组件
      withRouter,
      connect(commentSelector)
    )
    const EnhancedComponent = enhance(WrappedComponent)
  • 不要在render()中调用高阶函数
  • 将静态方法作拷贝,当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的全部静态方法。使用hoistNonReactStatic处理
  • 高阶组件返回组件命名

    function withSubscription(WrappedComponent) {
      class WithSubscription extends React.Component {/* ... */}
      WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
      return WithSubscription;
    }
    
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
  • refs不会被传递到被包裹组件,React16.3版本中加入React.forwardRef解决这个问题

    function logProps(Component) {
      class LogProps extends React.Component {
        componentDidUpdate(prevProps) {
          console.log('old props:', prevProps);
          console.log('new props:', this.props);
        }
    
        render() {
          const {forwardedRef, ...rest} = this.props;
    
          // Assign the custom prop "forwardedRef" as a ref
          return <Component ref={forwardedRef} {...rest} />;
        }
      }
    
      // Note the second param "ref" provided by React.forwardRef.
      // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
      // And it can then be attached to the Component.
      function forwardRef(props, ref) {
        return <LogProps {...props} forwardedRef={ref} />;
      }
    
      // These next lines are not necessary,
      // But they do give the component a better display name in DevTools,
      // e.g. "ForwardRef(logProps(MyComponent))"
      const name = Component.displayName || Component.name;
      forwardRef.displayName = `logProps(${name})`;
    
      return React.forwardRef(forwardRef);
    }

Render Props

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

是什么?

一个函数属性,用于渲染

属性名不必定是render,重要的是它起到的做用

注意

  • 在使用时最好指定propType
  • props检查优化,直接使用函数在render时会返回新函数;能够将函数定义为实例方法使用,从而避免这一问题

与第三方库协同

与jQuery协同,把DOM操做交给jQuery,React数据驱动渲染与jQuery DOM分离

可访问性

  • JSX支持全部的aria-* HTML属性
  • JSX中for特性被写做htmlFor

代码分割

import()动态导入 + react-loadable模块

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Loadable from 'react-loadable';

const Loading = () => <div>Loading...</div>;

const Home = Loadable({
  loader: () => import('./routes/Home'),
  loading: Loading,
});

const About = Loadable({
  loader: () => import('./routes/About'),
  loading: Loading,
});

const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
    </Switch>
  </Router>
);

API

createElement()

React.createElement(
  type,
  [props],
  [...children]
)

cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

element 做为起点,克隆并返回一个新的React元素(React Element)。生成的元素将会拥有原始元素props与新props的浅合并。新的子级会替换现有的子级。来自原始元素的 keyref将会保留。

isValidElement()

验证对象是不是一个React元素。返回 truefalse

React.Children

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)

ReactDOM

import ReactDOM from 'react-dom'

ReactDOM.render(
  element,
  container,
  [callback]
)
ReactDOM.findDOMNode(component)
相关文章
相关标签/搜索