🐧React

# 🐧React

# React知识体系

写业务

  • React基础
  • React Hooks
  • React Router
  • 状态管理容器
    • Redux
    • MobX
  • UI组件库
    • Ant Design of React

性能优化

  • React源码解析,相关原理

基础架构

  • 脚手架配置

# React学习资源

# 入门

入门学习阶段,参考学习资源——

21/10-11

这里看出来是初学了 有些啰嗦 回头重读下文档 精简一下

# 进阶

复读官方文档,高级部分、API部分、Hooks部分都可以涉及+项目+阅读源码

可以学习一些hooks3.0库的源码

阅读经典书籍

  • 《深入React技术栈》

# React基础知识

# HTML3

<!DOCTYPE html>
<html>
  <head>
    <script src="../build/react.js"></script>
    <script src="../build/react-dom.js"></script>
    <script src="../build/browser.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
      // ** Our code goes here! **
    </script>
  </body>
</html>

<script> 标签的 type 属性为 text/babel

这是因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"

上面代码一共用了三个库: react.jsreact-dom.jsBrowser.js ,它们必须首先加载。其中

  • react.js 是 React 的核心库
  • react-dom.js 是提供与 DOM 相关的功能
  • Browser.js 的作用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。

# ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('example')
);

上面代码将一个 h1 标题,插入 example 节点(查看 demo01 (opens new window)),运行结果如下。

img

# JSX

# JSX语法

HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法 (opens new window),它允许 HTML 与 JavaScript 的混写(查看 Demo02 (opens new window) )。

旧版本

var names = ['Alice', 'Emily', 'Kate'];

ReactDOM.render(
  <div>
  {
    names.map(function (name) {
      return <div>Hello, {name}!</div>
    })
  }
  </div>,
  document.getElementById('example')
);

新版本

涉及了key的内容

需要注意!如果涉及到遍历列表的需求,key最好不要用index!

image-20211026114735118

image-20211026114801860

小结:如果不存在对数据的逆序添加、逆序删除,仅仅是为了渲染列表用于展示,那就可以使用index作为key

image-20211026114904827

开发中如何选取key?

有唯一标识尽量还是用唯一标识!

image-20211026115023933

ReactDOM.render(
  <div>
  {
    names.map(function (name, index) {
      return <div key={index}>Hello, {name}!</div>
    })
  }
  </div>,
  document.getElementById('example')
);

上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。

img

# JSX中会展开数组

JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员(查看 demo03 (opens new window) )。

var arr = [
  <h1>Hello world!</h1>,
  <h2>React is awesome</h2>,
];
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById('example')
);

上面代码的arr变量是一个数组,结果 JSX 会把它的所有成员,添加到模板。

img

# React面向组件编程

# “组件类”

React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类(查看 demo04 (opens new window))。

旧版本

var HelloMessage = React.createClass({
  render: function() {
    return <h1>Hello {this.props.name}</h1>;
  }
});

ReactDOM.render(
  <HelloMessage name="John" />,
  document.getElementById('example')
);

新版本

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

组件“类”

上面代码中,变量 HelloMessage 就是一个组件类。模板插入 <HelloMessage /> 时,会自动生成 HelloMessage 的一个实例(下文的"组件"都指组件类的实例)

“自动生成组件类的一个实例”这个概念好像之前没听见过!学习了!

# 组件类使用render方法输出组件

所有组件类都必须有自己的 render 方法,用于输出组件。

注意,组件类的第一个字母必须大写,否则会报错,比如HelloMessage不能写成helloMessage

另外,组件类只能包含一个顶层标签,否则也会报错。

var HelloMessage = React.createClass({
  render: function() {
    return <h1>
      Hello {this.props.name}
    </h1><p>
      some text
    </p>;
  }
});

上面代码会报错,因为HelloMessage组件包含了两个顶层标签:h1p

# 组件实例从组件类的this.props对象获取属性

组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如 <HelloMessage name="John"> ,就是 HelloMessage 组件加入一个 name 属性,值为 John。组件的属性可以在组件类的 this.props 对象上获取,比如 name 属性就可以通过 this.props.name 读取。

# 组件属性名的注意点

添加组件属性,有一个地方需要注意,就是 class 属性需要写成 classNamefor 属性需要写成 htmlFor ,这是因为 classfor 是 JavaScript 的保留字。

# Props

# this.props.children-组件所有子节点

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点(查看 demo05 (opens new window))。

旧版本

var NotesList = React.createClass({
  render: function() {
    return (
      <ol>
      {
        React.Children.map(this.props.children, function (child) {
          return <li>{child}</li>;
        })
      }
      </ol>
    );
  }
});

ReactDOM.render(
  <NotesList>
    <span>hello</span>
    <span>world</span>
  </NotesList>,
  document.body
);

新版本

class NotesList extends React.Component{
    render(){
        return (
        	<ol>
            	{
                    React.Children.map(this.props.children, function(child){
                        return <li>{child}</li>
                    })
                }
            </ol>
        )
    }
}

上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取,运行结果如下。

img

#React.Children.map遍历子节点

这里需要注意, this.props.children 的值有三种可能:

  • 如果当前组件没有子节点,它就是 undefined ;
  • 如果有一个子节点,数据类型是 object
  • 如果有多个子节点,数据类型就是 array

所以,处理 this.props.children 的时候要小心。

React 提供一个工具方法 React.Children-中文文档 (opens new window) 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。更多的 React.Children 的方法,请参考官方文档-英文原版 (opens new window)

# PropTypes-验证使用组件时提供的参数是否符合要求

老版本中阮大使用 const Com React.createClass创建组件,与我之前学到的(更接近ES6规范的class写法)class Com extends React.Component 不太一样,且一些地方的用法也不同

可以看看这篇文章 (opens new window)所阐述的这两者之间的区别

    1、语法区别
    2、propType 和 getDefaultProps
    3、状态的区别
    4this区别
    5、Mixins

组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。

组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求(查看 demo06 (opens new window))。

老版本

通过proTypes对象和getDefaultProps()方法来设置和获取props.

var MyTitle = React.createClass({
    propTypes: {
        title:React.PropTypes.string.isRequired,
    },
    render: function(){
        return <h1>{this.props.title}</h1>
    }
})

新版本

通过设置两个属性propTypesdefaultProps

class MyTitle extends React.Component{
    //  改为属性
    static propTypes = {
        title: PropType.string.isRequired,
    }
	render(){
        return <h1> {this.props.title} </h1>;
    }
}

上面的Mytitle组件有一个title属性。PropTypes 告诉 React,这个 title 属性是必须的,而且它的值必须是字符串

举个反例来测试下

设置 title 属性的值是一个数值。

var data = 123;

ReactDOM.render(
  <MyTitle title={data} />,
  document.body
);

这样一来,title属性就通不过验证了。控制台会显示一行错误信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes设置,可以查看官方文档 (opens new window)

之前在fcc中学习到的 定义Props类型 (opens new window)是在组件类之外

上面学习到的直接在组件类里面定义也很是简单!

class ShoppingCart extends React.Component {
    constructor(porps){
        super(props);
    }
    render(){
        return <Items />
    }
}
const Items = (porps) => {
    return <h1>Current Quantity of Items in Cart:{props.quantity}</h1>
}
// 这里定义 propTypes 属性 验证组件是否接收了正确类型的 props
Items.propTypes = {quantity:PropTypes.number.isRequired};

# getDefaultProps-设置组件属性的默认值

旧版本

var MyTitle = React.createClass({
  getDefaultProps : function () {
    return {
      title : 'Hello World'
    };
  },

  render: function() {
     return <h1> {this.props.title} </h1>;
   }
});

ReactDOM.render(
  <MyTitle />,
  document.body
);

新版本

class MyTitle extends React.Component{
    static defaultProps = { 
        // as static property
        title: 'Hello World'
    };
    render(){
        return <h1>{this.props.title}</h1>
    }
}

上面代码会输出"Hello World"。

# State

# 组件与用户的互动-this.state

React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI (查看 demo08 (opens new window) )。

旧版本

var LikeButton = React.createClass({
  /*getInitialState: function() {
    return {liked: false};
  },*/
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

新版本

class LikeButton extends React.Component{
    /*constructor(props){
        super(props);
        this.state = {
            liked : false
        }
        this.handleClick = this.handleClick.bind(this);// 时刻记住要绑定这个方法
    }*/
    handleClick(event){
        this.setState({
            liked: !this.state.liked;
        })
    }
    render(){
		var text = this.state.liked ? 'like' : 'haven\'t liked';
        return(
            <p onClick={this.handleClick}>
        		You {text} this.Click to toggle
        	</p>
        )
    }
}

上面代码是一个 LikeButton 组件,

它的 getInitialState 方法用于定义——

  • 初始状态state(新版本中使用构造函数来定义state),也就是一个对象,这个对象可以通过 this.state 属性读取

当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

# 区分props与state

由于 this.propsthis.state 都用于描述组件的特性,可能会产生混淆。

一个简单的区分方法是:

  • this.props 表示那些一旦定义,就不再改变的特性
  • this.state 是会随着用户互动而产生变化的特性。

官方文档 (opens new window)是这么说的:

props(“properties” 的缩写)和 state 都是普通的 JavaScript 对象。 它们都是用来保存信息的,这些信息可以控制组件的渲染输出,而它们的一个重要的不同点就是:

  • props 是传递给组件的(类似于函数的形参)
  • 而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)

官方推荐阅读材料

# Refs (opens new window)

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素

在典型的 React 数据流中,props (opens new window) 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。

但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。

# 何时使用 Refs

下面是几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

举个例子,避免在 Dialog 组件里暴露 open()close() 方法,最好传递 isOpen 属性。

# 勿过度使用 Refs

你可能首先会想到使用 refs 在你的 app 中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。查看 状态提升 (opens new window) 以获取更多有关示例。

# 实例-创建 Refs

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();  }
  render() {
    return <div ref={this.myRef} />;  }
}

# 访问 Refs

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。

const node = this.myRef.current;

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

# 实例-将虚拟DOM插入文档,获取真实DOM

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。

根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff (opens new window) ,它可以极大提高网页的性能表现。

但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性(查看 demo07 (opens new window) )。

旧版本

var MyComponent = React.createClass({
    handleClick: function(){
        this.refs.myTextInput.focus();
    }
    render: function() {
    	return (
          <div>
            <input type="text" ref="myTextInput" />
            <input type="button" value="Focus the text input" onClick={this.handleClick} />
          </div>
        );
      }
})

需要注意,本例中提到的字符型的ref属性已经过时了 (opens new window)!(主要是因为效率不高+存在一些问题 (opens new window)

不过用这个的程序员可能还挺多 因为比较简单省事儿~

严谨起见,建议使用下面新版本中的回调函数方式 (opens new window)

新版本 (opens new window)

使用了React.createRef (opens new window),创建一个能够通过ref属性附加到 React 元素的 ref (opens new window)

class MyComponent extends React.Component{
    constructor(props){
        super(props);
        this.myTextInput = React.createRef();// myTextInput就是创建的Refs
        this.handleClick = this.handleClick.bind(this);// 绑定点击事件
    }
    handleClick(){
        //  点击事件的回调函数,作用:在点击按钮之后对“render中的元素”进行聚焦
        this.myTextInput.current.focus();
    }
    render(){
        return(
            <div>
                {/* 将ref传给render中的元素,对该节点(input输入框)的引用可以在ref的current属性中访问!见上面第9行的代码 */}
                <input type="text" ref={this.myTextInput} /> 
                <input type="button" value="Focus te text input" onClick={this.handleClick} />
            </div>
        )
    }
    
}

上面代码中,组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref 属性,然后 this.refs.[refName] 就会返回这个真实的 DOM 节点。

image-20211026114118072

需要注意的是,由于 this.refs.[refName] 属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。

上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会读取 this.refs.[refName] 属性。

React 组件支持很多事件,除了 Click 事件以外,还有 KeyDownCopyScroll 等,完整的事件清单请查看官方文档 (opens new window)

# 表单

# React中表单元素&DOM元素有些不同!

在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:

<form>
  <label>
    名字:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="提交" />
</form>

此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

# 使用受控组件访问用户填写的表单数据

受控组件的概念 (opens new window)

  • 在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 状态,并根据用户输入进行更新。
  • 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState() (opens new window)来更新。

把两者结合起来,使react得state成为“唯一数据源”;渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。

被React**以这种方式控制(用setState()更新表单属性值)**取值得表单输入元素就叫做——受控组件

用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props 读取(查看 demo9 (opens new window) )。

旧版本

没有构造函数看起来雀氏有点怪哈

var Input = React.createClass({
  getInitialState: function() {
    return {value: 'Hello!'};
  },
  handleChange: function(event) {
    this.setState({value: event.target.value});
  },
  render: function () {
    var value = this.state.value;
    return (
      <div>
        <input type="text" value={value} onChange={this.handleChange} />
        <p>{value}</p>
      </div>
    );
  }
});

ReactDOM.render(<Input/>, document.body);

新版本

这里我稍微改了一下state中属性的变量名,原来的跟event.target.value中的value重叠了有点难受XD

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            slogan: 'Hi!This is bill~'
        }
        this.handleChange = this.handleChange.bind(this)
    }
    handleChange(event) {
        this.setState({ slogan: event.target.value });
    }
    render() {
        var slogan = this.state.slogan;
        return (
            <div>
                <input type="text" value={slogan} onChange={this.handleChange} />
                <p>{slogan}</p>
            </div>
        );
    }
}

ReactDOM.render(<Input/>, document.getElementById('example'));

我们通过event.target.value来读取用户输入的值,并用这个值更新组件state中的对应属性(value)

上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,通过 event.target.value 读取用户输入的值

textarea 元素、select元素、radio元素都属于这种情况,更多介绍请参考官方文档 (opens new window)

  • 小结

受控组件使用state来存数据,创建回调函数并使用setState({数据:event.target.value})来更新数据,在表单中使用事件监听并调用回调函数。

# 受控组件的替代品-非受控组件

有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个React组件传递所有的输入state。

当你将之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,你可能希望使用非受控组件 (opens new window), 这是实现输入表单的另一种方式。

非受控组件 (opens new window)

大多数情况下,我们推荐使用 受控组件 (opens new window) 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理

有木有很耳熟?访问DOM节点来获取表单数据?

image-20211027111800991

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,我们可以 使用 ref (opens new window) 来从 DOM 节点中获取表单数据。

# 组件生命周期

# 组件生命周期的简单理解

  • 组件从创建到死亡它会经历一些特定的阶段。
  • React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用
  • 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

组件的生命周期 (opens new window)分成三个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

React 为每个状态都提供了处理函数,

  • will 函数在进入状态之前调用
  • did 函数在进入状态之后调用
  • unmount函数在结束状态时调用,做一些收尾工作

三种状态共计五种处理函数(钩子函数 即为生命周期回调函数)。

  • componentWillMount() ×
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState) ×
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

此外,React 还提供两种特殊状态的处理函数。

  • componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
  • shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用

这些方法的详细说明,可以参考官方文档 (opens new window)。下面是一个例子(查看 demo10 (opens new window) )。

旧版本

var Hello = React.createClass({
  getInitialState: function () {
    return {
      opacity: 1.0
    };
  },

  componentDidMount: function () {
    this.timer = setInterval(function () {
      var opacity = this.state.opacity;
      opacity -= .05;
      if (opacity < 0.1) {
        opacity = 1.0;
      }
      this.setState({
        opacity: opacity
      });
    }.bind(this), 100);
  },

  render: function () {
    return (
      <div style={{opacity: this.state.opacity}}>
        Hello {this.props.name}
      </div>
    );
  }
});

ReactDOM.render(
  <Hello name="world"/>,
  document.body
);

新版本

class Hello extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            opacity: 1
        }
    }
    componentDidMount(){
        // 在这个钩子函数中一般会进行一些初始化的事
        this.timer = setInterval(function(){
            var opacity = this.state.opacity;
            opacity -= 0.05;
            if(opacity < 0.1){
                opacity = 1.0;
            }
            this.setState({
                opacity: opacity
            })
        }.bind(this), 100);// 每隔0.1秒 将透明度减少0.05,并使用setState更新opacity透明度
    }
    render(){
        return(
            <div style = {{opacity: this.state.opacity}}>Hello {this.props.name}!</div>
        );
    }
}

上面代码在hello组件加载以后,通过 componentDidMount 方法设置一个定时器,每隔100毫秒,就重新设置组件的透明度,从而引发重新渲染。

另外,组件的style属性的设置方式也值得注意,不能写成

style="opacity:{this.state.opacity};"

而要写成

style={{opacity: this.state.opacity}}

这是因为 React 组件样式是一个对象,所以第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。

# 重要的生命周期钩子&即将废弃的钩子

# 重要(常用的)
  • render:初始化渲染或更新渲染调用
  • componentDidMount:用于在页面一上来就进行的一些操作,一般在这个钩子里做一些初始化的事。比如开启监听、开启定时器、 用来发送ajax请求
  • componentWillUnmount:做一些收尾工作, 如: 清理定时器
# 即将废弃
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

# 生命周期流程图(旧)&生命周期三个阶段

image-20211027113050692

【1】ReactDOM.render()触发---初次渲染

  • constructor()
  • componentWillMount()生命周期方法会在版本 16.X 废弃在版本 17 移除
  • render()——常用(这肯定)
  • componentDidMount() —— 常用
    • 用于在页面一上来就进行的一些操作,一般在这个钩子里做一些初始化的事 比如:
      • 开启定时器
      • 发送网络请求
      • 订阅消息
    • 【来自freecodecamp】某些时候,大多数 web 开发人员需要调用 API 接口来获取数据。 如果正在使用 React,知道在哪里执行这个动作是很重要的。所以React 的最佳实践是在生命周期方法 componentDidMount() 中对服务器进行 API 调用或任何其它调用 (opens new window)。 将组件装载到DOM 后会调用此生命周期方法。
      • 此处对 setState() 的任何调用都将触发组件的重新渲染。 在此方法中调用 API 并用 API 返回的数据设置 state 时,一旦收到数据,它将自动触发更新。

【2】由组件内部this.setSate()或父组件从render触发 —更新阶段

其实就是上面最后一点说的 “对setState的任何调用都会触发组件的重新渲染(即为更新)”

  • shouldComponentUpdate()
  • componentWillUpdate() - 即将废弃
  • render()
  • componentDidUpdate()

【3】由ReactDOM.unmountComponentAtNode()触发 — 卸载组件

  • componentWillUnmount() ——常用 一般在这个钩子中做一些收尾的事情 例如:
    • 关闭定时器
    • 取消订阅消息

# 生命周期流程图(新)&新生命周期的三个阶段

image-20211027144203366

【1】ReactDOM.render()触发---初次渲染

  • constructor()
  • componentWillMount()生命周期方法会在版本 16.X 废弃在版本 17 移除
  • render()——常用(这肯定)
  • componentDidMount() —— 常用 ,好比“人刚出生”

新增:

  • getDerivedStateFromProps

【2】由组件内部this.setSate()或父组件从render触发 —更新阶段

  • shouldComponentUpdate()
  • componentWillUpdate()- 废弃
  • render()
  • componentDidUpdate()

新增:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

【3】由ReactDOM.unmountComponentAtNode()触发 — 卸载组件

  • componentWillUnmount() ——常用,好比“人的寿命走向终点”

# React Ajax

# 通过Ajax请求从服务器获取数据并传入组件

组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI (查看 demo11 (opens new window) )。

旧版本

var UserGist = React.createClass({
  getInitialState: function() {
    return {
      username: '',
      lastGistUrl: ''
    };
  },

  componentDidMount: function() {
    $.get(this.props.source, function(result) {
      var lastGist = result[0];
      if (this.isMounted()) {
        this.setState({
          username: lastGist.owner.login,
          lastGistUrl: lastGist.html_url
        });
      }
    }.bind(this));
  },

  render: function() {
    return (
      <div>
        {this.state.username}'s last gist is
        <a href={this.state.lastGistUrl}>here</a>.
      </div>
    );
  }
});

ReactDOM.render(
  <UserGist source="https://api.github.com/users/octocat/gists" />,
  document.body
);

新版本

class UserGist extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            username: '',
            lastGistUrl: ''
        }
    }
    componentDidMount(){
        $.get(this.props.source, function(result){
            var lastGist = result[0];
            this.setState({
                username: lastGist.owner.login,
                lastGistUrl: lastGist.html_url
            });
        }.bind(this));
    }
    render(){
		return(
            <div>
                {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>.
        	</div>
        );
    }
}
ReactDOM.render(
    <UserGist source="https://api.github.com/users/octocat/gists" />,
    document.getElementById('example')
);

上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖,完全可以不用jQuery,而使用其他库。

# 将promise对象传入组件

我们甚至可以把一个Promise对象传入组件,请看Demo12 (opens new window)

ReactDOM.render(
  <RepoList
    promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
  />,
  document.body
);

上面代码从Github的API抓取数据,然后将Promise对象作为属性,传给RepoList组件。

如果Promise对象正在抓取数据(pending状态),组件显示"正在加载";

如果Promise对象报错(rejected状态),组件显示报错信息;

如果Promise对象抓取数据成功(fulfilled状态),组件显示获取的数据。

class RepoList extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            loading: true,
            error: null,
            data: null
        };
    }

    componentDidMount() {
        this.props.promise.then(
            value => this.setState({loading: false, data: value}),
            error => this.setState({loading: false, error: error}));
    }

    render() {
        // 请求中
        if (this.state.loading) {
            return <span>Loading...</span>;
        }
        // 请求失败
        else if (this.state.error !== null) {
            return <span>Error: {this.state.error.message}</span>;
        }
        // q
        else {
            var repos = this.state.data.items;
            var repoList = repos.map(function (repo, index) {
                return (
                    <li key={index}><a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}</li>
                );
            });
            return (
                <main>
                    <h1>Most Popular JavaScript Projects in Github</h1>
                    <ol>{repoList}</ol>
                </main>
            );
        }
    }
}
  • 请求中

image-20211027160808722

  • 请求失败

image-20211027160738510

  • 请求成功

image-20211027160759180

# React原理

# 虚拟DOM与DOM Diffing算法

Diffing 算法 (opens new window)

比对两个虚拟DOM时会有三种操作:删除、替换、更新

对比两棵树时 React会首先比较两棵树的根节点,不同类型的根节点元素会有不同的形态。

  • 删除:newVnode不存在
  • 替换:vnode和newVnode类型不同或key不同
  • 更新:有相同类型和key,但vnode和newVnode不同

首次调用render进行页面渲染 将容器节点中的所有DOM元素都替换掉,后续进行更新渲染时则会使用React的diffing算法进行高效更新~

# diff算法高效在哪里?

这种自上而下的React Node节点更新按理来说是很笨重的 使用diff算法结合fiber架构 使更新变为分片的,不会一直占据着JS引擎 导致阻塞渲染线程、卡顿

# React页面挂载过程

image-20220119123500688

  • 组件被称为"状态机", 通过更新组件的state(状态)来更新对应的页面显示(重新渲染组件 - 每次调用render 就会渲染组件)

# 状态管理-MobX,redux

十分钟入门 MobX & React (opens new window)

搭配官方文档&API文档&实际项目中的代码食用最佳~

# 类组件

# 类组件获取DOM

# 大神绘图 React 生命周期 (opens new window)

componentWillMounted - 渲染前的生命周期——对应哪个钩子? 22/3/25 字节二面提到的内容

image-20220325165806126

image.png

# React Hooks

# 函数式组件获取DOM

# useMemo & useCallback

# useEffect

# 轻松学会 React 钩子:以 useEffect() 为例 - 阮一峰的网络日志 (opens new window)

# useState与useRef的区别

# useRef (opens new window)

  • const renderCount = useRef(0)将变量存在 renderCount.current 属性中
    • 特点:改变这个属性不会引发组件重新渲染
  • 在input中使用 const inputRef = useRef + <input ref={inputRef}>
    • inputRef.current 可以获取到对应的input元素

# 两个钩子的区别

  • setState函数会更新state。它接收一个新的state值并将组件的一次重新渲染加入任务队列。

  • ref对象内容发生变化时,useRef不会通知我们

    • 也就是说变更 .current 属性不会引发组件重新渲染
    • 当我们想存储一个值并不希望更新值的时候触发页面的渲染时,可以使用useRef