The Road to learn React

发布时间 2023-10-07 16:14:17作者: Felix_Openmind

React基础

组件内部状态

组件内部状态也称之为局部状态,允许保存、修改和删除存储在组件内部的属性
使用ES6类组件可以在构造函数种初始化组件的状态,构造函数只会在组件初始化的时候调用一次

const list = [
  {
    title: 'React',
    url: 'https://facebook.github.io/react/',
    author: 'Jordan Walke',
    num_comments: 3,
    points: 4,
    objectID: 0,
  },
]

class App extends Component {
  constructor(props) {
    super(props);
    // state通过使用this绑定在类上,在整个组件中访问到state
    // 在render()方法中映射一个在组件外定义静态列表,之后就可以在组件中使用state里面的list
    this.state = {
       list: list
    }
    // list是组件的一部分,驻留在组件的state中,可以从list中添加、修改或删除
  }

  render() {
    return (
      <div className='App'>
        {
          this.state.list.map(item => 
              <div key={item.objectId}>
                <span>
                  <a href={item.url}>{item.title}</a>
                </span>
                <span>{item.author}</span>
                <span>{item.num_comments}</span>
                <span>{item.points}</span>
              </div>
          )
        }
      </div>
    )
  }
}

ES6对象初始化

const name = 'Robin';

const user = {
  name: name, // bad
}

// 当对象中的属性名和变量名相同的时候

const user = {
  name,
}

// 删除点击项
onDismiss(id) {
    const isNotId = item => item.objectId !== id;
    const updatedList = this.state.list.filter(isNotId);
    this.setState({ list: updatedList });
}

箭头函数实现自动绑定

class ExplainBindingsComponent extends Component {
  onClickMe = () => {
    console.log(this);
  }

  render() {
    return (
      <button onClick={this.onClickMe} type='button'>
        Click Me
      </button>
    )
  }
}

Component Declarations 组件声明

React中不同的组件类型

函数式无状态组件

组件即函数,接收一个输入并返回一个输出,输入的是props,输出的是一个普通的JSX组件实例
函数式无状态组件是函数,并且没有本地状态。即没有this对象也没有生命周期方法。

类组件中constructor()和render()都是属于生命周期方法,constructor在一个组件的生命周期中只会执行一次,render()方法会在最开始执行一次,并且每次组件更新的时候都会执行。
函数式无状态组件是没有生命周期方法的

ES6类组件

在类的定义中,其继承自React组件。extend会注册所有的生命周期方法,
只要在React component API中,均可以在组件中使用。通过这种方式使用render()类方法
通过使用this.state和this.setState()可以在ES6类组件中存储和操作state

React.createClass

React.createClass

使用真实的API

生命周期方法

constructor(构造函数)只有在组件实例化并插入到DOM中的时候才会被调用。组件实例化的过程称之为组件的挂载(mount)
render()方法也会在组件挂载的过程中被调用,同时当组件更新的时候也会被调用。每当组件的状态(state)或属性(props)改变的时候,组件的render()方法都会被调用
在组件挂载过程中还存在另外两个生命周期方法: componentWillMount() 和 componentDidMount()
构造函数(constructor)最先执行,componentWillMount()会在render()方法之前执行,而componentDidmount会在render()方法之后执行
在挂载过程中存在4个生命周期方法,其调用的顺序如下:

* constructor()
* componentWillMount() - render()方法之前调用,可以设置组件内部状态,不会触发组件再次渲染,推荐在constructor中初始化状态
* render() - 返回作为组件输出的元素,这个方法是一个纯函数,不应该在这个方法中修改组件的状态。它把属性和状态作为输入并且返回(需要渲染)的元素
* componentDidMount() - 仅仅在组件挂载后执行一次,★发起异步请求去API获取数据的绝佳时期。获取到的数据将保存在内部组件的状态中之后在render()生命周期方法中展示出来。

当组件的状态或属性改变的时候用来更新组件的生命周期存在5个生命周期方法用于组件更新,调用顺序如下:

* componentWillReceiveProps(nextProps) - 组件初始化的时候被调用,可以用来设置初始化状态以及绑定类方法
* shouldComponentUpdate(nextProps, nextState) - 每次组件因为状态或属性更改而更新的时候都会被调用,将在成熟的React应用中来进行性能优化。在一个更新生命周期中,组件及其子组件将根据方法返回的布尔值来决定是否重新渲染。可以阻止组件的渲染生命周期(render lifecycle)方法,避免不必要的渲染。
* componentWillUpdate() - render()执行之前的最后一个方法,已经拥有下一个属性和状态,利用这个方法在渲染之前进行最后的准备这个生命周期方法不能再触发setState(),如果想要基于新的属性计算状态则必须利用componentWillReceiveProps()
* render()
* componentDidUpdate(prevProps, prevState) - 这个方法在render()之后立即调用,可以当作操作DOM或执行更多异步请求的机会

组件卸载同样存在生命周期,只有一个生命周期方法: componentWillUnmount

componentWillUnmount(): 在组件销毁之前被调用,利用这个生命周期方法来执行任何清理任务。

componentDidCatch(error, info): 用来捕获组件的错误。

获取数据

class App extends Component {
  
  constructor(props) {
    super(props);
    this.state = {
      result: null,
      searchTerm: DEFAULT_QUERY,
    };

    this.setSearchTopStories = this.setSearchTopStories.bind(this);
    this.fetchSearchTopStories = this.fetchSearchTopStories.bind(this);
    this.onSearchChange = this.onSearchChange.bind(this);
    this.onDismiss = this.onDismiss.bind(this);
  }

  setSearchTopStories(result) {
    this.setState({ result });
  }

  fetchSearchTopStories(searchTerm) {
    fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${searchTerm}`)
      .then(response => response.json())
      .then(result => this.setSearchTopStories(result))
      .catch(e => e);
  }

  componentDidMount() {
    const { searchTerm } = this.state;
    this.fetchSearchTopStories(searchTerm);
  }
}

高级React组件

引用DOM元素

React中与DOM节点进行交互,【ref】属性可以让我们访问元素中的一个节点。
访问DOM节点是React中的一种反模式,遵循声明式编程和单向数据流。
需要访问DOM节点的三种情况

  1. 使用DOM API(focus事件,媒体播放等)
  2. 调用命令式DOM节点动画
  3. 和需要DOM节点的第三方继承

无状态组件和ES6类组件都可以使用ref属性,在聚焦input字段的用例中,需要一个生命周期方法。

class Search extends Component {
  render() {
    value,
    onChange,
    onSubmit,
    children,
  } = this.props;

  return (
    <form onSubmit={onSubmit}>
      <input type='text' value={ value } onChange={ onChange }/>
      <button type='submit'>
        { children }
      </button>
    </form>
  )
}

ES6类组件的this对象可以帮助我们利用ref属性引用DOM节点

class Search extends Component {
  render() {
    const {
      value,
      onChange,
      onSubmit,
      children,
    } = this.props;
  }
  
  return (
    <form onSubmit={onSubmit}>
      <input type='text' value={value} onChange={onChange}
          ref={(node) => { this.input = node; }}/>
      <button type='submit'>
        { children }
      </button>
    </form>
  )

}

加载

请求是异步的,此时需要展示某些事情即将发生的某种反馈, =》 定义一个可重用的Loading组件

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

class App extends Component {
  
  constructor(props) {
    super(props);
    this.state = {
      results: null,
      searchKey: '',
      searchTerm: DEFAULT_QUERY,
      error: null,
      isLoading: false,
    }
  }
// isLoading的初始值是false,在App组件挂载完成之前,无需加载任何东西
  
  render() {
    const {
      searchTerm,
      results,
      searchKey,
      error,
      isLoading
    } = this.state;
  }

  return (
    <div className='page'>
      <div className='interactions'>
        {
          isLoading 
            ? <Loading/>
            : <Button onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}> More </Button>
        }
      </div>
    </div>
  )
}

在componentDidMount()中发起请求,Loading组件会在应用程序启动的时候显示,
返回的数据会通过Table组件显示出来,加载状态(isLoading)设置为false,之后Loading组件消失

高阶组件 - 将一个组件作为输入并返回一个组件

高阶组件(HOC)是React中一个高级概念,HOC和高阶函数是等价的

  • example01
function withFoo(Component) {
  return function(props) {
    return <Component { ...props }/>
  }
}

惯例:用“with”前缀来命名HOC,使用ES6箭头函数更简介的表达HOC

const withFoo = (Component) => (props) => <Component { ...props }/>

增强组件案例:当加载状态(isLoading)为true的时候,组件显示Loading组件,否则显示输入的组件

const withLoading = (Component) => (props) => props.isLoading ? <Loading/> : <Component { ...props }/>

基于加载属性(isLoading),可以实现条件渲染,该函数将返回Loading组件或输入的组件。
将对象展开之后作为一个组件的输入非常高效

// before you would have to destructure the props before passing them
const { foo, bar } = props;
<SomeComponent foo={ foo } bar={ bar }/>

// but you can use the object spread operator to pass all object properties
<SomeCompontnt { ...props }/>

// isLoading属性在内的所有props通过展开对象传递给输入的组件
// 优化

const widthLoading = (Component) => ({ isLoading, ...rest }) => 
    isLoading
      ? <Loading/>
      : <Component { ...rest }/>

实现一个ButtonWithLoading组件

const Button = ({ onClick, className = '', children }) =>
    <button
      onClick={ onClick }
      className = { className }
      type = 'button
    >
      {children}
    </button>

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

const withLoading = (Component) => ({ isLoading, ...rest }) => 
    isLoading
      ? <Loading/>
      : <Component { ...rest }/>
const ButtonWithLoading = withLoading(Button);

ButtonWithLoading组件,接收加载状态(isLoading)作为附加属性,当HOC消费加载属性(isLoading)的时候,将所有其他props传递给Button组件

class App extends Component {
  render() {
    return (
      <div className='page'>
        <div className='interactions'>
          <ButtonWithLoading
              isLoading={ isLoading }
              onClick={() => this.fetchSearchTopStories(searchKey, page + 1)}>
            More
          </ButtonWithLoading>
        </div>
      </div>
    )
  }
}
// 高阶组件是React中的高级技术,提高组件复用性、抽象性、组合性以及对props、state和视图的可操作性。

React中高级排序

客户端和服务器端搜索交互,

import React, { Component } from 'react';
import fetch from 'isomorphic-fetch';
import { sortBy } from 'lodash'
import './App.css'

const SORTS = {
  NONE: list => list,
  TITLE: list => sortBy(list, 'title'),
  AUTHOR: list => sortBy(list, 'author'),
  COMMENTS: list => sortBy(list, 'num_comments').reverse(),
  POINTS: list => sortBy(list, 'points').reverse();
};
// App组件负责存储排序函数的状态。组件的初始状态存储的是默认排序函数,不对列表排序只是将输入的list作为输出                            
class App extends Component {
   this.state = {
      results: null,
      searchKey: '',
      searchTerm: DEFAULT_QWUERY,
      error: null,
      isLoading: false,
      sortKey: 'NONE',
   }      

   render() {
      const {
        searchTerm,
        results,
        searchKey,
        error,
        isLoading,
        sortKey
      } = this.state
   }

    return (
      <div className='page'>
        <Table list={list} sortKey={sortKey} onSort={this.onSort} onDismiss={this.onDismiss}/>
      </div>
    )                       
}

Table组件负责对列表排序,通过sortKey选取SORT对象中对应的排序函数,并列表作为该函数的输入

const Table = ({
  list,
  sortKey,
  onSort,
  onDismiss
}) => 
  <div className='table'>
    {SORTS[sortKey](list).map(item => 
    <div key={item.objectId} className='table-row'>
       // ...
    </div>
  )}
  </div>
> 列表可以按照其中的任意排序函数进行排序

const Sort = ({ sortKey, onSort, children }) => 
  <Button
    onClick={() => onSort(sortKey)}
    className='button-inline'
  >
    {children}
  </Button>