学习React第一节课:基础

前言

文档目录:

  1. 梦开始的地方
  2. 如何创造一个组件
  3. 属性 props
  4. 状态 state
  5. 生命周期 lifecycle

次文档为入门文档,暂时或以后不要用到的知识都给省略了

平时基本不用的尽量省略

优化相关尽量省略

React 17 相关尽量省略


一、梦开始的地方

react 文件一般用 .jsx 后缀来命名,但是也可以用 .js,两者从结果来说并无区别。

同理,如果是引入了 typescript,还可以用 .tsx 或者 .ts 来命名

特殊的文件名后缀只是为了方便 webpack 以及 ide 的识别,其并无特殊意义

入口文件(main)的编写

import React from "react"; // 每个 react 文件都必须引入 react 库,否则无法识别 react 语法
import ReactDOM from "react-dom";

// 声明(创建)一个简单的组件
function MyFirstComponent() {
  return <div>hello world!</div>;
}

// 将组件渲染到实际 DOM 节点上
ReactDOM.render(
  <MyFirstComponent></MyFirstComponent>,
  document.getElementById("root")
);

二、如何创造一个组件

有且只有两种方式来构造一个组件

  • 函数式(function component)
  • 类定义(class component)

基础用法

下面分别用来两种方式来构造(渲染结果)一模一样的组件

import React from "react";

function MyFuncComp(props) {
  console.log(props);
  return <div>hello world!</div>;
}

class MyClassComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return <div>hello world!</div>;
  }
}

演示两者的区别

从可以实现的效果来说,两者的主要区别:

  • Function Component
    1. 没有生命周期钩子
    2. 无 state
  • Class Component
    1. 有生命周期钩子
    2. 有 state

从 react 17 开始,Function Component 均可以通过不同的 Hook 来实现 Class Component 可以实现的功能

目前为止,你只需要了解最基本的编写方法即可,暂且用上面所表述的那样来区别两者

如何用 Hooks 来实现 React Class Component 写法

class MyClassComp extends React.Component {
  constructor(props) {
    super(props);

    // 在构造函数中初始化 state
    this.state = {
      num: 0,
    };
  }

  // 各种生命周期钩子
  componentWillMount() {}
  componentDidMount() {}
  // shouldComponentUpdate(){}

  // 声明响应点击的方法
  handleAddClick = () => {
    // Class Component 通过 setState 方法来改变 state
    this.setState({
      num: this.state.num + 1,
    });
  };
  render() {
    // 内部除了 props,还有 state
    console.log(this.props, this.state);
    return (
      <div>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

三、属性 props

父组件通过 props 传递属性(或方法)给子组件

function Child(props) {
  return <div>父组件传递的props:{props.test}</div>;
}
function Parent() {
  return <Child test="123"></Child>;
}

子节点 children

props 中有一个特殊的属性 children 用以存放父组件传递的子节点

并且 children 的值有三种可能:

  1. undefined 当前组件没有子节点
  2. object 当前组件有一个子节点
  3. array 当前组件有多个子节点

关于 react 中的一些关于操作节点的 api,这里就不展开了

React.ChildrenReact.cloneElement 等等

import React from "react";

function Child(props) {
  // const items=React.Children.only(props.children)   // 只能返回一个children(对象),若children不止一个,则会抛出异常
  // const items=props.children.map(item=>item);      // 当children不止一个时,props.children为数组;只有一个时,为对象,所以props.children.map方法并不适用任何情况
  // const items=React.Children.map(props.children,item=>item);    // 适用与任一情况
  const items = React.Children.toArray(props.children); // 等价于上面一行
  return (
    <div>
      <p>父组件传递的一般属性:{props.test}</p>
      <div>父组件子节点:{props.children}</div>
      <div>父组件子节点:{items}</div>
    </div>
  );
}
function Parent() {
  return (
    <Child test="123">
      <p>子节点1</p>
      <p>子节点2</p>
    </Child>
  );
}

默认 props

通过 defaultProps 静态属性来定义该组件的默认 props

// Function Component
function MyCompOne(props) {
  return <div>{props.myStr}</div>;
}
MyCompOne.defaultProps = {
  myStr: "hello world",
};

// Class Component
class MyCompOne extends React.Component {
  static defaultProps = {
    myStr: "hello world",
  };
  render() {
    return <div>{this.props.myStr}</div>;
  }
}

其他

相关还有其他知识点,这里就不展开了

  1. props 的校验
  2. 方法传递时 this 指向问题
  3. 解构方式传递 props

四、状态 state

state 是一个组件的数据模型,是组件渲染时的数据依据,它与 props 共同来推动视图的渲染

改变 state

react 中通过 setState 来触发 state 的变化

有一下几点需要注意的:

  1. 不允许直接通过赋值语句来改变 state 的值
  2. 不是每次调用 setState 都会触发一次 render 的,state 所触发的更新是异步的
  3. 不要将“改变不需要触发重绘”的数据放在 state 中,这里会放到“性能优化”中详细介绍
class MyClassComp extends React.Component {
  constructor(props) {
    super(props);

    // 在构造函数中初始化 state
    this.state = {
      num: 0,
    };
  }

  // 声明响应点击的方法
  handleAddClick = () => {
    // Class Component 通过 setState 方法来改变 state
    this.setState({
      num: this.state.num + 1,
    });
  };

  render() {
    return (
      <div>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

五、生命周期 lifecycle

不同生命周期在组件的不同阶段被调用的顺序

这是 React 16 前的流程,后续也就只是针对 will 类型的钩子进行修改,目前开发过程中尽量不使用 will 之类的生命周期即可

<table border="1">
<thead>
<tr >
<th style="text-align: center;">Initialization</th>
<th style="text-align: center;">Mounting</th>
<th colspan="2" style="text-align: center;">Updation</th>
<th style="text-align: center;">Unmounting</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">setup props and state</td>
<td>
<ol>
<li>componentWillMount</li>
<li>render</li>
<li>componentDidMount</li>
</ol>
</td>
<td>
<ol>
<li>componentWillReceiveProps</li>
<li>shouldComponentUpdate</li>
<li>componentWillUpdate</li>
<li>render</li>
<li>getSnapshotBeforeUpdate</li>
<li>componentDidUpdate</li>
</ol>
</td>
<td>
<ol>
<li>shouldComponentUpdate</li>
<li>componentWillUpdate</li>
<li>render</li>
<li>getSnapshotBeforeUpdate</li>
<li>componentDidUpdate</li>
</ol>
</td>
<td>componentWillUnmount</td>
</tr>
</tbody>
</table>

componentDidMount

组件挂载到 DOM 后调用,且只会被调用一次

一般就是在这里做一些诸如”发请求获取详情“等逻辑

function timeout(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

class ChildComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false,
      name: "",
    };
  }

  async componentDidMount() {
    // 展示 loading 效果
    this.setState({
      loading: true,
    });
    await timeout(1000); // 用一个延时来模拟异步请求
    this.setState({
      loading: false,
      name: "张大炮",
    });
  }

  render() {
    const { loading, name } = this.state;
    if (loading) {
      return <div>加载中...</div>;
    } else {
      return <div>我的名字:{name}</div>;
    }
  }
}

shouldComponentUpdate

当组件的更新机制被触发时,判断组件是否需要重新渲染(rerender)

触发组件的更新机制有以下几种场景:

  1. 父组件 rerender
  2. 自身 state 发生改变

当 state/props 发生变化时肯定会进入 shouldComponentUpdate,但是反之不是

譬如,当父组件导致子组件更新时,即使子组件接收的 props 是否发生改变,子组件都会触发更新机制

class ChildComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0,
    };
  }

  handleAddClick = () => {
    this.setState({
      num: this.state.num + 1,
    });
  };

  // shouldComponentUpdate(nextProps, nextState) {
  //   // 每次 props 或者 state 的改变都会进入本方法
  //   return true; // 方法需要传递一个 boolean 的返回值,true 表示“要渲染”
  // }
  shouldComponentUpdate(nextProps, nextState) {
    // 判断前后的 props 或 state 是否发生了变化,来决定是否要重新渲染
    return (
      this.props.test !== nextProps.test || this.state.num !== nextState.num
    );
  }

  render() {
    return (
      <div>
        <p>父组件传递的属性:{this.props.test}</p>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

shouldComponentUpdate 提供了组件渲染的优化方式,可以不写,其默认处理方式就是“始终返回 true”

还有一种内置 shouldComponentUpdate 的 Class Component,为 React.PureComponent,目前可以不需要了解,这里就不展开了

Class Component 还有一个 this.forceUpdate() 的内置方法,用于强制触发重新渲染(不用经过 shouldComponentUpdate),这里就不展开了,自行了解

componentDidUpdate

在组件重新渲染完成之后调用,并且首次渲染不会调用

componentDidUpdate(prevProps, prevState, snapshot){
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

你可以在 componentDidUpdate 中调用 setState,但请注意,它必须像上面的例子那样被包装在一个条件中,否则有可能会导致无限循环。

不建议将父组件传递的 props 复制到子组件的 state 上,将 props 拷贝到 state 上会导致 bug

componentWillUnmout

在组件被销毁前会进入本生命周期

在这里一般需要进行以下操作:

  1. 清除掉你在本组件中使用的定时器 clearTimeout clearInterval
  2. 移除掉你在本组件中挂在的监听 removeEventListener
  3. 中止异步请求
class MyComp extends React.Component {
  myInterval; // 要用一个属性来接收使用到的定时器
  constructor(props) {
    super(props);
    this.state = {
      second: 0,
      size: "",
    };
  }
  componentDidMount() {
    // 绑定定时器
    this.myInterval = window.setInterval(
      () =>
        this.setState({
          second: this.state.second + 1,
        }),
      1000
    );

    // 绑定监听
    window.addEventListener("resize", this.handleWindowResize);

    // 发送异步请求
    // TODO
  }
  componentWillUnmount() {
    // 清除定时器
    window.clearInterval(this.myInterval);

    // 清除监听
    window.removeEventListener("resize", this.handleWindowResize);

    // 取消(未完成的)异步请求
    // TODO
  }
  handleWindowResize = () => {
    this.setState({
      size: `${window.innerWidth}*${window.innerHeight}`,
    });
  };
  render() {
    return (
      <div>
        <p>倒计时:{this.state.second}</p>
        <p>页面尺寸:{this.state.size}</p>
      </div>
    );
  }
}

其他

static getDerivedStateFromPropsgetSnapshotBeforeUpdate,应用面少,这里不展开了

在 react 17 中,以下几个生命周期将会被修改,并且必须加上 UNSAFE_ 才行:

  • UNSAFE_componentWillMount 初始化逻辑应该在 componentDidMount 上实现
  • UNSAFE_componentwillReceiveProps 原来功能建议在 componentDidUpdate 上实现
  • UNSAFE_componentWillUpdate 这个实在想不出来能在什么地方用

这些 UNSAFE 的生命周期在后续还会被考虑直接移除,所以新的项目能不使用就不要使用了

至于为什么这些 componentWill 之类的东西要考虑被移除,可以去了解 React Fiber

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容