react 基础知识总结

react 基础知识总结
介绍

虚拟 DOM 的本质:使用 JS 对象模拟 DOM 树。
虚拟 DOM 的目的:为了实现 DOM 节点的高效更新。
React 内部已经帮我们实现了虚拟 DOM,初学者掌握如何调用即可。
diff 算法
怎么实现 两颗新旧 DOM 树的对比 呢?这里就涉及到了 diff 算法。常见的 diff 算法如下:
tree diff:新旧 DOM 树,逐层对比的方式,就叫做 tree diff。每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素。
component diff:在对比每一层的时候,组件之间的对比,叫做 component diff。当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置。
element diff:在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff。
key:key 这个属性,可以把 页面上的 DOM 节点 和 虚拟 DOM 中的对象,做一层关联关系。

jsx

JSX 的全称
JSX:JavaScript XML,一种类似于 XML 的 JS 扩展语法。也可以理解成:符合 XML 规范的 JS 语法。
需要注意的是,哪怕你在 JS 中写的是 JSX 语法(即 JSX 这样的标签),但是,JSX 内部在运行的时候,并不是直接把 我们的 HTML 标签渲染到页面上;而是先把 类似于 HTML 这样的标签代码,转换成 React.createElement 这样的 JS 代码,再渲染到页面中。

babel 转换工具
如果要直接使用 JSX 语法,需要先安装相关的 语法转换工具:
运行 cnpm i babel-preset-react -D
这个 babel 包的作用是:将 JSX 语法 转换为 JS 语法。

var vDom = (
<div>
Hello, React!
<p className="test">test</p>
<label htmlFor="" />
</div>
);

(4)在 JSX 创建 DOM 的时候,所有的节点,必须有唯一的根元素进行包裹。
(5)如果要写注释,注释必须放到 {} 内部。例如:

 // 使用JSX语法 创建虚拟DOM对象
    var vDom = (
    // 这一行是注释
    <div>
      Hello, React!
      <p className="qianguyihao">千古壹号</p>
      {/*这一行也是注释 */}
    </div>
    );

上面的内容里,我们使用了两种方式创建组件。这两种方式,有着本质的区别,我们来对比一下。

对比:
方式一:通过 function 构造函数 创建组件。内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据。

方式二:通过 class 创建子组件。内部除了有 this.props 这个只读属性之外,还有一个专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的。
基于上面的区别,我们可以为这两种创建组件的方式下定义: 使用 function 创建的组件,叫做【无状态组件】;使用 class 创建的组件,叫做【有状态组件】。
本质区别:
有状态组件和无状态组件,最本质的区别,就是有无 state 属性。同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数。

生命周期

组件的生命周期
在组件创建、到加载到页面上运行、以及组件被销毁的过程中,总是伴随着各种各样的事件,这些在组件特定时期,触发的事件统称为组件的生命周期。
生命周期的阶段
组件生命周期分为三个阶段,下面分别来讲解。

1、组件创建阶段

组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次。
getDefaultProps
初始化 props 属性默认值。
getInitialState
初始化组件的私有数据。因为 state 是定义在组件的 constructor 构造器当中的,只要 new 了 class 类,必然会调用 constructor 构造器。
componentWillMount()
组件将要被挂载。此时还没有开始渲染虚拟 DOM。
在这个阶段,不能去操作 DOM 元素,但可以操作属性、状态、function。相当于 Vue 中的 Create()函数。
render()
第一次开始渲染真正的虚拟 DOM。当 render 执行完,内存中就有了完整的虚拟 DOM 了。
意思是,此时,虚拟 DOM 在内存中创建好了,但是还没有挂在到页面上。
在这个函数内部,不能去操作 DOM 元素,因为还没 return 之前,虚拟 DOM 还没有创建;当 return 执行完毕后,虚拟 DOM 就创建好了,但是还没有挂在到页面上。
componentDidMount()
当组件(虚拟 DOM)挂载到页面之后,会进入这个生命周期函数。
只要进入到这个生命周期函数,则必然说明,页面上已经有可见的 DOM 元素了。此时,组件已经显示到了页面上,state 上的数据、内存中的虚拟 DOM、以及浏览器中的页面,已经完全保持一致了。
当这个方法执行完,组件就进入都了 运行中 的状态。所以说,componentDidMount 是创建阶段的最后一个函数。
在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。如果我们想操作 DOM 元素,最早只能在 componentDidMount 中进行。相当于 Vue 中的 mounted() 函数

2、组件运行阶段

有一个显著的特点,根据组件的 state 和 props 的改变,有选择性的触发 0 次或多次。
componentWillReceiveProps()
组件将要接收新属性。只有当父组件中,通过某些事件,重新修改了 传递给 子组件的 props 数据之后,才会触发这个钩子函数。
shouldComponentUpdate()
判断组件是否需要被更新。此时,组件尚未被更新,但是,state 和 props 肯定是最新的。
componentWillUpdate()
组件将要被更新。此时,组件还没有被更新,在进入到这个生命周期函数的时候,内存中的虚拟 DOM 还是旧的,页面上的 DOM 元素也是旧的。(也就是说,此时操作的是旧的 DOM 元素)
render
此时,又要根据最新的 state 和 props,重新渲染一棵内存中的 虚拟 DOM 树。当 render 调用完毕,内存中的旧 DOM 树,已经被新 DOM 树替换了!此时,虚拟 DOM 树已经和组件的 state 保持一致了,都是最新的;但是页面还是旧的。
componentDidUpdate
此时,组件完成更新,页面被重新渲染。此时,state、虚拟 DOM 和 页面已经完全保持同步。

3、组件销毁阶段

一辈子只执行一次。
componentWillUnmount: 组件将要被卸载。此时组件还可以正常使用。

生命周期对比:
vue 中的生命周期图
React Native 中组件的生命周期
组件生命周期的执行顺序
1、Mounting:

constructor()

componentWillMount()

render()

componentDidMount()

2、Updating:

componentWillReceiveProps(nextProps):接收父组件传递过来的属性

shouldComponentUpdate(nextProps, nextState):一旦调用 setState,就会触发这个方法。方法默认 return true;如果 return false,后续的方法就不会走了。

componentWillUpdate(nextProps, nextState)

render()

componentDidUpdate(prevProps, prevState)

3、Unmounting:

componentWillUnmount()

this 绑定

绑定 this 的方式一:bind()
代码举例:

import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);

    this.state = {
      msg: "这是 MyComponent 组件 默认的msg"
    };

}

render() {
return (

<div>
<h1>绑定 This 并传参</h1>
{/_ bind 的作用:为前面的函数,修改函数内部的 this 指向。让 函数内部的 this,指向 bind 参数列表中的 第一个参数 _/}
<input
          type="button"
          value="绑定this并传参"
          onClick={this.changeMsg1.bind(this)}
        />
<h3>{this.state.msg}</h3>
</div>
);
}

changeMsg1() {
this.setState({
msg: "设置 msg 为新的值"
});
}
}

上方代码中,我们为什么用 bind(),而不是用 call/apply 呢?因为 bind() 并不会立即调用,正是我们需要的。
注意:bind 中的第一个参数,是用来修改 this 指向的。第一个参数后面的所有参数,都将作为函数的参数传递进去。
绑定 this 并给函数传参 的方式二:构造函数里设置 bind()
我们知道,构造函数中的 this 本身就是指向组件的实例的,所以,我们可以在这里做一些事情。
代码举例:

import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);

    this.state = {
      msg: "这是 MyComponent 组件 默认的msg"
    };

    // 绑定 this 并给函数传参的方式2: 在构造函数中绑定并传参
    // 注意:当一个函数调用 bind 改变了this指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变this指向后的函数的引用。
    // 也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的this指向。
    this.changeMsg2 = this.changeMsg2.bind(this, "千古恩", "壹号恩");

}

render() {
return (

<div>
<h1>绑定 This 并传参</h1>
<input type="button" value="绑定this并传参" onClick={this.changeMsg2} />
<h3>{this.state.msg}</h3>
</div>
);
}

changeMsg2(arg1, arg2) {
this.setState({
msg: "设置 msg 为新的值" + arg1 + arg2
});
}
}

上方代码中,需要注意的是:当一个函数调用 bind 改变了 this 指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变 this 指向后的函数的引用。也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的 this 指向。
绑定 this 并给函数传参 的方式三:箭头函数【荐】
第三种方式用得最多。

代码举例:

import React from "react";

export default class MyComponent extends React.Component {
constructor(props) {
super(props);

    this.state = {
      msg: "这是 MyComponent 组件 默认的msg"
    };

}

render() {
return (

<div>
<h1>绑定 This 并传参</h1>
<input
type="button"
value="绑定 this 并传参"
onClick={() => {
this.changeMsg3("千古 3", "壹号 3");
}}
/>
<h3>{this.state.msg}</h3>
</div>
);
}

changeMsg3 = (arg1, arg2) => {
// console.log(this);
// 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined
this.setState({
msg: "绑定 this 并传参的方式 3:" + arg1 + arg2
});
};
}
单向数据绑定

在 Vue 中,可以通过 v-model 指令来实现双向数据绑定。但是,在 React 中并没有指令的概念,而且 React 默认不支持 双向数据绑定。
React 只支持,把数据从 state 上传输到页面,但是,无法自动实现数据从 页面 传输到 state 中 进行保存。

react 路由

React 路由的使用
使用 React 路由之前,我们需要先安装 react-router-dom 这个包。比如:
yarn add react-router-dom
代码举例:
(1)index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <!-- 容器,通过 React 渲染得到的 虚拟DOM,会呈现到这个位置 -->
  <div id="app"></div>
</body>
</html>

(2)main.js:

// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";

import App from "./App.jsx";

// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(<App />, document.getElementById("app"));

(3)app.jsx:

import React from "react";

// 如果要使用 路由模块,第一步,运行 yarn add react-router-dom
// 第二步,导入 路由模块

// HashRouter 表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了;
// Route 表示一个路由规则, 在 Route 上,有两个比较重要的属性, path   component
// Link 表示一个路由的链接 ,就好比 vue 中的 <router-link to=""></router-link>
import { HashRouter, Route, Link } from "react-router-dom";

import Home from "./components/Home.jsx";
import Movie from "./components/Movie.jsx";
import About from "./components/About.jsx";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    // 当 使用 HashRouter 把 App 根组件的元素包裹起来之后,网站就已经启用路由了
    // 在一个 HashRouter 中,只能有唯一的一个根元素
    // 在一个网站中,只需要使用 唯一的一次 <HashRouter></HashRouter> 即可
    return (
      <HashRouter>
        <div>
          <h1>这是网站的APP根组件</h1>
          <hr />

          <Link to="/home">首页</Link>&nbsp;&nbsp;
          <Link to="/movie">电影</Link>&nbsp;&nbsp;
          <Link to="/about">关于</Link>
          <hr />

          {/* Route 创建的标签,就是路由规则,其中 path 表示要匹配的路由,component 表示要展示的组件 */}
          {/* 在 vue 中有个 router-view 的路由标签,专门用来放置,匹配到的路由组件的,但是,在 react-router 中,并没有类似于这样的标签,而是 ,直接把 Route 标签,当作的 坑(占位符) */}
          {/* Route 具有两种身份:1. 它是一个路由匹配规则; 2. 它是 一个占位符,表示将来匹配到的组件都放到这个位置 */}
          <Route path="/home" component={Home} />
          <hr />
          <Route path="/movie" component={Movie} />
          <hr />
          <Route path="/about" component={About} />
        </div>
      </HashRouter>
    );
  }
}

(4)ReactDemo/src/components/Home.jsx

import React from "react";

export default class Home extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    return <div>Home组件</div>;
  }
}

(5)ReactDemo/src/components/Movie.jsx

import React from "react";

export default class Movie extends React.Component {
    constructor(props) {
        super(props);
        this.state = {};
      }

  render() {
    return <div>Movie组件</div>;
  }
}

(6)ReactDemo/src/components/About.jsx

import React from "react";

export default class About extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  render() {
    return <div>About组件</div>;
  }
}

匹配路由参数

模糊匹配与精准匹配

我们在上面的代码中,进一步修改。假设 Movie 这个组件修改成这种路由匹配方式:

<Link to="/movie/top250">电影</Link>

<Route path="/movie" component={Movie} />

上面这种匹配方式,也是可以成功匹配到的。这是为啥呢?

这是因为:默认情况下,路由中的匹配规则,是模糊匹配的。如果 路由可以部分匹配成功,就会展示这个路由对应的组件。

如果想让路由规则,进行精确匹配,可以为 Route 添加 exact 属性。比如下面这种写法,因为是开启了精准匹配,所以是匹配不到的:(无法匹配)

<Link to="/movie/top250/20">电影</Link>

<Route path="/movie/" component={Movie} exact/>

另外,如果要匹配参数,可以在匹配规则中,使用 : 修饰符,表示这个位置匹配到的是参数。举例如下:(匹配正常)

电影  

获取路由参数

继续修改上面的代码。如果我想在 Movie 组件中显示路由中的参数,怎么做呢?

我们可以通过 props.match.params 获取路由中的参数。举例做法如下:

app.jsx 中的匹配规则如下:

<Link to="/movie/top100/5">电影</Link>&nbsp;&nbsp;

<Route path="/movie/:type/:id" component={Movie} exact/>

Moivie 组件的写法如下:

import React from "react";

export default class Movie extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      routeParams: props.match.params // 把路由中的参数保存到 state 中
    };
  }

  render() {
    console.log(this);
    // 如果想要从路由规则中,提取匹配到的参数,进行使用,可以使用 this.props.match.params.*** 来访问
    return (
      <div>
        {/* Movie --- {this.props.match.params.type} --- {this.props.match.params.id} */}
        Movie --- {this.state.routeParams.type} --- {this.state.routeParams.id}
      </div>
    );
  }
}

BrowserRouter
<BrowserRouter>
  <Switch>
    {routers.map((router, index) => {
      return (
        <Route
          path={router.path}
          component={router.component}
          key={index}
        ></Route>
      );
    })}
  </Switch>
</BrowserRouter>

const routers = [
  {
    path: "/test1",
    component: Test1,
  },
  {
    path: "/test2",
    component: Test2,
  },
  {
    path: "/",
    component: Test1,
  },
];

希望路由匹配到一个只会,就不要向下匹配了。
添加一个 Switch 组件
Switch 里面只能包路由 Route
把包裹 Route 的元素换成 Switch 就好
路由导航
Link
NavLink
这 2 个都是组件,用法也都一样,唯一的就是:NavLink 可以给选中的按钮配置高亮样式
默认都是 a 标签
不支持指定标签名
to 路径
使用 NavLink 要注意当碰到首页是一个**/的时候加 exact 属性严格匹配,不然会出现多个元素都有高亮**类名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶落风尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值