## React 详细文档
1. React 特点:
声明式编程:
(1) 声明式编程现在是目前整个大前端开发模式: Vue React Flutter SwiftUI;
(2) 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
组件化开发:
(1) 组件化开发页面目前前端的流行趋势, 我们会将复杂的界面拆分为一个个小组件
(2) 如何合理的进行组件的划分和设计也是后面我会讲到的一个重点;
多平台适配:
(1) 2013年,React 发布之处主要是开发Web 页面;
(2) 2015年, Facebook推出了 ReactNative,用于开发移动端跨平台(目前虽然Flutter非常火爆,但是还是有很多公司在使用ReactNative)
(3) 2017年,Facebook推出ReactVR,用于开发虚拟现实Web应用程序,
2. React 开发依赖
React开发必须依赖三个库
(1) React: 包含react所必须的核心代码
(2) react-dom: react渲染在不同平台所需要的核心代码
(3) babel: 将jsx转换成React代码工具
babel是什么呢?
Babel,又名叫Babel.js
- 是目前前端使用非常广泛的编辑器, 转移器
- 比如当下很多浏览器并不支持ES6的语法, 但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它
- 那么编写源码时我们就可以使用ES6来编译, 之后通过Babel工具, 将ES6转成大多数浏览器都支持的ES5语法
React和Babel的关系:
- 默认情况下开发React其实可以不使用babel
- 但是目前提示我们自己使用React.createElement来编写源代码, 他编写的代码非常的繁琐和可读性差
- 那么我们就可以直接编写jsx(JavaScript XML) 的语法,并且让babel帮助我们转换成React.createElement.
第一次接触React 会被他繁琐的依赖搞蒙,对于Vue 来说,我们只是依赖一个Vue.js文件即可,但是React居然要依赖三个库
(4) 其实呢,这三个库是各司其职的,目前就是让每个库单纯做自己的事情
(5) 在React的0.14版本之前低没有react-dom 这个概念的,所有的功能都包含在react里
(6) 为什么要进行拆分? 原因就是react-native
(7) react包中 包含了 react和 react-native所共同拥有的核心代码
react-dom 针对web和native所完成的事情不同
(8) web端: react-dom 会讲究jsx 最终渲染成真是的DOM 显示在浏览器中
(9) native端: react-dom 会将jsx 最终渲染成原生的控件 (比如Android中的Button,IOS中的UButton)
引入依赖
- 方式一: 直接CDN引入
- 方式二: 下载后,添加本地依赖
- 方式三: 通过npm 管理(脚手架后续使用)
ES6的class
在ES6之前,我们通过function来定义类,
认识JSX
const element = Hello world
;
ReactDOM.render(element, document.querySelector("#root"))
这段element变量的声明右侧赋值的标签语法是什么呢?
- 他不是一段字符串(因为没有使用引号包裹),他看起来是一个HTML原生, 但是我们能在js 中直接给一个变量赋值html吗?
- 其实是不可以的,如果我们讲type="text/babel"去掉,name就会出现语法错误;
- 他到底是什么呢? 其实就是一段JSX的语法
JSX是什么?
- JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起来就是一段XML语法;
- 他用于描述我们的UI界面, 并且其完成可以和JavaScript融合在一起使用
- 他不同于Vue 中的模块语法,不需要专门学习模块语法中的一些指令(v-for,v-if,v-else,v-bind)
为什么React选择JSX
React认为渲染逻辑本质上与其他UI逻辑存在内在的耦合
- 比如UI需要绑定事件(button,a原生等等)
- 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI
他们之间是密不可分的,所以React没有讲标记分离到不同的文件中,而是将他们组合到了一起,这个地方就是组件(component)
其实JSX就是嵌入到JavaScript中的一种结构语法
JSX的书写规范
- JSX的顶层只能有一个根元素, 所以很多时候会在外层包裹一个div原生
- 为了方便阅读 通常在JSX的外层包裹一个小括号,这样可以方便阅读,并且JSX可以进行转换书写
- JSX中的标签可以是单标签,也可以是双标签
如果是单标签的话,必须要/> 结尾;
JSX 的使用
JSX的注释写法:{/注释的语法结构/}
JSX嵌入变量
(1) 情况一:当为Number String Array 类型时, 可以直接显示
(2) 情况二:当变量是null,undefined,Boolean类型时,内容为空
如果希望可以显示null,undefined,Boolean,那么需要转换成字符串;
转换的方式很多,比如toString方法,和空字符串拼接,String(变量)等方式
(3) 情况三:对象类型不能作为子元素(not valid as React child)JSX 嵌入表达式
(1) 运算表达式
(2) 三元运算符
(3) 执行一个函数
JSX绑定属性
- 比如元素都会有title属性
- 比如元素img会有src属性
- 比如a元素会有href属性
- 比如元素可能会绑定calss
- 比如原生使用内联样式style
JSX的本质
实际上,JSX仅仅是React.createElement(component,props,...children)函数的语法糖.
所有的JSX最终都会转换为React.createElement的函数调用.
-
createElement需要传递三个参数
-- 参数一:type
当前的ReactElement的类型;
如果是标签元素, 那么就是用字符串表示"div";
如果是组件元素,那么就直接使用组件的名称;--参数二:config
所有的JSX中的属性都在config中以对象的属性和值的形式储存--参数三:children
存放在标签中的内容,以children数组的方式进行储存
当然,如果是多个元素,React内部有对它们进行处理,
虚拟DOM的创建过程
我们通过React.createElement最终创建出来一个ReactElement对象
这个ReactElement对象是什么作用呢? React为什么要创建它呢?
-- 原因是React利用ReactElement对象组成了一个JavaScript的对象树
-- JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM)为什么使用虚拟DOM
-- 很难跟踪状态的改变: 原有的开发模式,我们很难跟踪到状态的改变,不方便针对我们的应用程序进行调试,
-- 操作真是DOM性能较低:传统的开发模式进行频繁的DOM操作,而这一做法性能非常的低;DOM操作性能非常低
-- 首先,document.createElement本身创建出来的就是一个非常的复杂的对象
http://developer.mozilla.org/zh-CN/docs/Web/API/Document?createElement
-- 其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作声明式编程
<1> 虚拟DOM帮助我们从命令式编程到了声明式编程的模式
<2> React官方说法: Virtual DOM 是一种编程理念,
-- 在这个理念中,UI以一种理想话或者说虚拟话的方式保存在内存中,并且它是一个相对简单的JavaScript对象
-- 我们可以通过ReactDOM.render() 让虚拟DOM和真实DOM同步起来, 这个过程中叫做协调(Reconciliation)
前端脚手架
对于现在比较流行的三大框架都有属于自己的脚手架
--Vue的脚手架:vue-cli
--Angular的脚手架:angular-cli
--React的脚手架:create-react-app他们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好
使用这些脚手架需要依赖什么呢?
--目前这些脚手架都是使用node编写的,并且都是基于webpack的;
--所以必须在自己的电脑上安装node环境Yarn 和 npm 命令的对比
Npm
npm install
npm install[package]
npm install --save[package]
npm install --save-dev[package]
npm rebuild
npm uninstall[package]
npm uninstall --save[package]
npm uninstall --save-dev[package]
npm uninstall --save-optional[package]
npm cache clean
rm-rf node_modules && npm install
Yarn
yarn install
yarn add[package]
yarn add[package]
yarn add[package][--dev/-D]
yarn install --force
yarn remove[package]
yarn remove[package]
yarn remove[package]
yarn remove[package]
yarn cache clean
yarn upgrade
-
React 安装脚手架 命令如下:
--0.在国内,某些情况使用npm和yarn可能无法正常的安装一个库,这个时候我们就可以选择使用cnpm
--CNPM安装命令: npm install -g cnpm --registry=https://registry.npm.taobao.org--1.0 NodeJs的安装网址: https://nodejs.org/en/download
--1.2 查看node 是否安装成功: node --version
--1.3.yarn安装命令:npm install -g yarn
查看yarn/npm 的版本是否安装成功: yarn/npm --version--1.4.React 项目的脚手架安装命令: npm install -g create-react-app
查看版本命令: create-react-app --version--1.5.创建React项目的命令:
// 创建方式一:
create-react-app 项目名称(项目名称,不能写大写字母)--1.6. 项目创建好了以后:
cd 02_learn_scaffold
yarn start (类似于Vue的npm run serve)
需要使用yarn安装各类依赖的话:
yarn add axios
React 项目结构 详细解说
1. node_modules: 所有依赖的总集合包,和vue的是一样的
2. public {
favicon.ico:图标
index.html:每个项目的入口,单页面复应用
manifest.json: 和web app配置相关
logo192.png:图片而已
robots.txt:设置爬虫规则的
}
3. src { // 写的所有的源代码文件的
App.css: 当前的App组件的 css 样式
App.js:App组件的代码文件(函数式组件)
App.test.js: 对App写一些测试用例的
index.css: 写全局样式的
index.js: 整个应用程序的入口js文件
logo.svg: 项目刚启动时看到的当前页面旋转的那个SVG图片
reportWebVitals.js 默认帮我们写好的注册PWA相关代码
setupTests.js:测试初始化文件
}
4. .gitignore(这个文件的主要工作是:忽略一些不需要提交到代码仓库的文件就在这里写,不需要共享的文件写在这里)
5. package.json(关于我们整个项目管理配置的一个文件)
6. README.md 说明文档
7. yarn.lock (记录真实版本的依赖)
当我们创建好了脚手架以后 需要把 Src 文件夹中的所有的文件全部删掉
- 创建 index.js 文件 里面需要写以下代码
// 第一步:
import React from 'react';
//第二步:
import ReactDOM from "react-dom"
// 导入你封装的 js 文件
import { sum } from "./utils"
console.log(sum(10, 20));
// 第三步
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0
}
}
render() {
return (
当前计数
)
}
}
// 第三步:
// ReactDOM.render(需要挂载的组件名称, 这个地方会找到你的pubic 里面的 index.html中的 文件)
ReactDOM.render( , document.querySelector('#root'))
这种写到 index.js 中是不规范的,所以 要重新写一个 App.js 文件 把 第三步 抽取到 App.js 文件中
实际代码截图:
PWA
- PWA全称Progressive Web App,即渐进式WEB应用
- 一个PWA应用首先是一个网页,可以通过Web技术编写出一个网页应用
- 随后添加上 App Manifest 和 Service Worker 来实现PWA的安装和离线等功能
- 这种Web存在的形式,我们称为Web App
PWA 解决了哪些问题呢?
- 可以添加到主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
- 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
- 实现了消息推送
- 等等一系列类似于Native App 相关功能
webpack 是什么 ?
webpack是一个现代化JavaScript 应用程序的静态模块打包器
当webpack 处理 应用程序时,他会递归构建一个依赖关系图,其中包含应用程序需要的每个模块 然后将所有这些模块打包成一个或多个bundle;
-
想要暴露出webpack的 配置显示在文件中的话 可以执行命令:
-- yarn eject(1) 如果你要是在脚手架创建好了以后的话,去修改项目中的文件 会出现 一个提示,
你就要执行 一下的命令:-- git add .
-- git commit -m "代码修改"
分而治之的思想 -- 也就是组件化开发
/********* 类组件 start ******************/
类组件的定义有如下要求:
-- 组件的名称是大写字符开头(无论是类组件还是函数组件)
-- 类组件需要继承自 React.Component
-- 类组件必须实现render函数-
在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议我们使用ES6的class类定义
使用class定义组件:
-- constructor是可选的,我们通常在constructor中初始化一些数据
-- this.state中维护的就是我们组件内部的数据
-- render() 方法是class组件中唯一必须实现的方法 render函数的返回值
(1) 当render 被调用时,他会检查 this.props 和 this.state的变化并返回以下类型之一
(2) React 元素:
- 通常通过JSX 创建
- 例如 会被React 渲染为DOM 节点,
会被React传染为自定义组件; - 无论是 还是
均为React 元素
定义React 代码块的 快捷方式: rcc(类组件)/rfc(函数组件)
如果你不想返回一个根组件的话, 数组或者 fragments: 使得render 方法可以返回多个元素
那么可以这种写:
import React,{Component} from 'react';
import React, { Component } from 'react';
class App extends Component {
render() {
return (
[
Hello World
Hello React
]
);
}
}
export default App;
Portals:可以渲染子节点 到不同的DOM子树中
字符串或数值类型:他们在DOM 中会被渲染为文本节点
布尔类型或 null: 什么都不渲染
/********* 类组件 end ******************/
/********* 函数组件 start ******************/
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中的render函数返回一样的内容
-
函数组件有自己的特点
-- 没有生命周期,也会被更新并挂载,但是没有生命周期函数
-- 没有this(组件实例)
-- 没有内部状态(state)
/********* 函数组件 end ******************/
认识生命周期
生命周期和生命周期的关系:
--生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多的阶段
(1) 比如挂载阶段(Mounting),组件第一次在DOM树中被渲染的过程
在挂载阶段要执行的生命周期函数:
constructor 函数
render(){}
React updates DOM and refs
componentDidMount (已经挂载成功)
(2) 比如在更新过程(Updataing),组件状态发生变化,重新更新渲染的过程
New props setStete() forceUpdate()
render() {}
React updates DOM and refs
componentDidUpdate (已经发生更新)
(3) 比如卸载过程(Unmounting),组件从DOM树中被移除的过程
Unmounting
componentWillUnmount (即将被移除掉)React内部为了告诉我们当前处于那些阶段,会对我们组件内部实现的某些函数进行回调, 这个就是函数的生命周期函数
(1)比如实现componentDidMount函数: 组件已经挂载到DOM上时,就会回调
(1.1) componentDidMoun() 会在组件挂载后(插入DOM树中)立即调用
--依赖DOM的操作可以在这里进行
--在此处发送网络请求就是最好的地方
--可以在此处天机一些订阅(会在componentWillUnmount取消订阅)
(2)比如实现componentDidUpdate函数: 组件已经发生了更新时,就会回调
(2.1) componentDidUpdate函数: 组件已经发生了更新时,就会回调,首次渲染不会执行此方法
-- 当组件更新后,可以在此处对DOM进行操作
-- 如果你对更新前后的props进行了比较,也可以选择此处进行网络请求;(例如props未发生变化时,则不会执行网络请求)
(3)比如实现componentWillUnmount函数: 组件即将被移除,就会回调
(3.1) componentWillUnmount() 会在组件的卸载以及销毁之前直接调用的
-- 在此方法中执行必要的清理操作
-- 例如,清楚timer,取消网路请求或清除,在componentWillUnmount中创建的订阅等
- 我们谈React 声明周期时,主要谈的是类的生命周期, 因为函数式组件是没有生命周期函数的;(后面我们可以通过hooks来模拟一些生命周期的回调)
3.1. getDerrivedStateFromProps的使用 -- (如果当前的construtor中的state里面的数据永远依赖别的组件传递过来的,如果你同时希望发生变化的话那么你就可以使用这个函数来同步)
3.2 shouldComponentUpdate的使用 --(这个是决定我们的render函数到底需不需要渲染)
3.3 getSnapshotBeforeUpdate的使用 -- (这个可以获取你更新之前的一些数据的)
-
Constructor
(1)如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数(2) Constructor
-- 通过this.state赋值对象来初始化内部的state
-- 为事件绑定实例( this )
认识组件的嵌套
import React, { Component } from 'react';
// Header
function Header() {
return 我是Header
}
// main
// main 里面还有细节的拆分
function Banner() {
return 我是轮播图组件
}
function Products() {
return (
- 商品列表1
- 商品列表2
- 商品列表3
- 商品列表4
- 商品列表5
- 商品列表6
)
}
function Main() {
return (
我是Main组件
)
}
// Footer
function Footer() {
return 我是Footer组件
}
class App extends Component {
render() {
return (
);
}
}
export default App;
组件之间的通讯
<一> 父组件传子组件:
父组件通过 属性 = 值的形式来传递给子组件数据;
子组件通过props参数获取父组件传递过来的数据
-
参数的验证 对于传递子组件的数据, 有的时候我们可能希望进行验证, 特别是对于大型项目来说
-- 当然,如果你的项目中默认集成了Flow或者TypeScript,那么直接就可以进行类型验证
-- 但是即使我们没有使用 Flow或者TypeScript,也是可以通过prop-types库来进行参数验证-- 从React v15.5开始, React.propTypes已移入另外一个包中:prop-types库
<二>子传父
- 在React中同样的是通过 props 传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可
<三>非父子组件的传递
-
React.createContext
-- 创建一个需要共享的Context对象
-- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的Provider中读取到当前的context值;
-- defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值const MyContext = React.createContext(defaultValue)
Context.Provider
--每个Context对象都会返回一个Provider React组件,它允许消费组件订阅context 的变化
--Provider接收一个value属性,传递给消费组件
--一个Provider可以和多个消费组件有对应关系;
--多个Provider也可以嵌套使用,里层覆盖外层的数据
--当Provider的value值发生变化时,它内部的所有消费组件都会被重新渲染class.contextType
--挂载在class上的contextType属性会被重新赋值为一个由React.createContext() 创建Context对象
--这能让你使用this.context来消费Context上的那个值
--你可以在任何的生命周期访问他,包括在render函数中也是可以的UserContext.Consumer
-- 函数式的专用语法:
{
value => {
return (
用户昵称: {value.nikename}
用户等级: {value.level}
)
}
}
为什么使用setState
因为修改了 state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生变化,
React并没有实现类似于Vue的Object.defineProtype或者Vue3的Proxy的方式监听数据的变化
我们必须通过setState的方法告知数据已经发生变化了
在组建中并没有实现setState的方法,为什么可以调用了?
--原因很简单,setState方法是从Component中继承过来的setSate 这个是一个异步的更新
-
为什么setState 设计为异步?
-- https://github.com/facebook/react/issues/11527#issuecomment-360199710--总结: 1. setState设计为异步,可以显著的提升性能; 2. 如果每次调用setState都进行一次更新,那么意味着render函数会被繁琐调用,界面重新渲染,这样效率很低; 3. 最好的办法应该是获取到多个更新,之后进行批量的更新 4. 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步 5. state和props不能保持一致性,会在开发中产生很多问题 PS: setState在那些情况是同步那些是异步呢? -- 在组件生命周期或React合成时间中,setState是异步; -- 在setTimeout或者原生dom事件中,setState是同步
React更新机制
-- 我们在前面已经学习React 的渲染流程
JSX ---> 虚拟DOM --->真是DOM
-- 那么React的更新流程呢?
Props/state改变 --> render函数重新执行 --> 产生新的DOM树-->新旧DOM树进行diff -->计算出差异进行更新-->更新到真实的DOM上
- 情况一: 当节点为不同的元素,React会拆卸原有的树,并且简历起新的树;
-- 当一个元素从 变为,从
-- 当卸载一颗树时,对应的DOM节点也会被销毁,组件实例将执行componentWillUnmount() 方法
-- 当简历一颗新的树时,对应的DOM节点会创建以及插入到DOM中,组件实例将执行componentWillMount() 方法,紧接着componentDidMount()方法
比如下面代码更改:
React会销毁Counter组件并且重新装载一个新的组件,而不会对Counter进行复用
- 情况二:对比同一类型的元素
-- 当对比两个相同的类型的React元素时,React会保留DOM节点,仅比对及更新与改变的属性
-- 比如下面的代码更改
通过比对这两个元素,React知道需要修改DOM元素上的className;
比如下面的代码更改:
当更新style属性时,React仅更新有所更变的属性
通过比对这两个元素时,React知道只需要修改DOM元素上的color样式,无需修改fontWeight.
如果是同类型的组件元素:
--组件会保持不变,React会更新改组件的props,并且调用componentWillReceiveProps()和componentWillUpdate()方法
--下一步,调用render()方法,diff算法会将在之前的结果以及新的结果进行递归情况三:对子节点进行递归
--在默认条件下,当递归DOM节点的子元素时,React会同时遍历两个子元素的列表,当产生差异时,生成一个mutation
--前面两个比较时完全相同的,所以不会产生mutation;
--左后一个比较,产生一个mutation,将其插入到新的DOM树中即可
- first
- second
- first
- second
- third
- 但是如果我们是在中间插入一条数据:
React会对每个子元素一个mutation,而不是保持 - 星际穿越 和
- 盗墓空间 的不变,这种低效的比较方式会带来一定的性能问题
- 星际穿越
- 盗墓空间
- 大话西游
- 星际穿越
- 盗墓空间
keys的优化
我们在前面的遍历列表时,总会提示一个警告,让我们加入key属性
方式一: 在最后面插入数据
这种情况,有无key意义并不大方式二: 在前面插入数据
这种做法,在没有key 的情况下,所有的li 都需要进行修改当子元素(这里的li) 拥有key时,React使用key为111 和 222的元素仅仅进行移位,不需要进行任何的修改
将key为333 的元素插入到最前面的位置即可
- key的注意事项:
-- key应该是唯一的
-- key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
-- 使用index作为可以,对性能是没有优化的
render 函数的调用
- 当你有很多的组件都嵌套在App里面的时候 例如:
import React, { Component } from 'react';
class Banner extends Component {
render() {
return 我是中间部分
}
}
class HeaderBox extends Component {
render () {
return (
我是头部标签
)
}
}
function Main() {
return (
)
}
class App extends Component {
render() {
return (
);
}
}
export default App;
这样的话就会你每次在点击button + 1 的时候 他会把所有的组件都会重新的渲染一遍,这样很影响性能, 所以就要用shouldComponentUpdata这个生命周期函数
PS: 这个生命周期不是随便乱用的,这个是当你需要阻断更新的地方你就阻断,不应该阻断的地方你就不要阻断
shouldComponentUpdate生命周期的使用--这个是类特有的生命周期:
React 给我们提供一个生命周期方法 shouldComponentUpdate(很多时候简称SCU),这个方法接受参数,并且需要有返回值
该方法有两个参数
-- 参数一:nextProps修改之后,最新的props属性
-- 参数二:nextState修改之后,最新的state属性该方法返回的是一个Boolean类型
-- 返回值为true,那么就需要调用render方法
-- 返回值为false,那么就不需要调用render方法
-- 默认返回的是true,也就是只要state发生了变化,就会调用render方法比如我们在App中增加一个message属性
--jsx中没有依赖这个message,那么他的改变不应该引起重新渲染
-- 但是因为render监听到state的改变,就会重新render,所以最后render方法还是被重新调用了
如果每个组件都有这种情况的话,那是不是每个组件中都要写shoundComponentUpdate呢?
不是的,你只需要继承PureComponent 这个就可以解决所有的问题 (这个仅限用于类组件上面的,函数式组件是不行的需要用到 memo)
PureComponent 的使用
import React, { Component } from 'react';
// 所以不管你里面有多少组件 就不需要写shoundComponentUpdate生命周期了
// 也不需要在生命周期中去写判断了
// 直接去让你的组件去继承 PureComponent 这个就行了
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0,
message: "Hello,world"
}
}
// 这样的话就会你每次在点击button + 1 的时候 他会把所有的组件都会重新的渲染一遍,
// 这样很影响性能, 所以就要用shouldComponentUpdata这个生命周期函数
// 这个生命周期不是随便乱用的,这个是当你需要阻断更新的地方你就阻断,
// 不应该阻断的地方你就不要阻断,所以这个地方你要做判断
// shouldComponentUpdate(nextProps,nextState) // 里面会有2个参数的
// 最新的 nextProps 和最新的 nextState
// 如果还有其他的需要通过的那么你就需要继续在下面判断了
shouldComponentUpdate(nextProps, nextState) {
// console.log(nextProps);
// console.log(nextState);
// return true // 只要这个地方写true的话就是所有的组件需要更新
// return false // 这个就不会更新了
if (this.state.count !== nextState.count) {
return true
}
return false
}
render() {
console.log("App render函数调用");
return (
当前计数: {this.state.count}
);
}
addNumber() {
this.setState({
count: this.state.count + 1
})
}
changeText() {
this.setState({
message: "哈哈哈"
})
}
}
export default App;
PS: 所以不管你里面有多少组件 就不需要写shoundComponentUpdate生命周期了,也不需要在生命周期中去写判断了,
直接去让你的组件去继承 PureComponent 这个就行了 (这个仅限用于类组件上面的,函数式组件是不行的需要用到 memo)
memo的使用:
// 1.导入memo 高阶组件
import React, { PureComponent,memo } from 'react';
class Header extends PureComponent {
render() {
return 我是头部标签
}
}
// 2. 使用 memo
const MemoHeader = memo(function Products () {
return (
- 我是商品列表一
- 我是商品列表二
- 我是商品列表三
- 我是商品列表四
- 我是商品列表五
)
})
function Main() {
return (
{/* 挂载memo */}
)
}
class App extends PureComponent {
render() {
return (
);
}
}
export default App;
setState传递的数据需要不可变的数据
import React, { PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
nameList: [
{ name: "lilei", age: 20 },
{ name: "hanmeimei", age: 28 },
{ name: "chuchuhu", age: 25 },
]
}
}
// shouldComponentUpdate(newProps, newState) {
// if (newState.nameList !== this.state.nameList) {
// return true
// }
// return false
// }
render() {
return (
好友列表
{
this.state.nameList.map((item, index) => {
return (
-
姓名:{item.name}
年纪:{item.age}
)
})
}
);
}
addData() {
// const newData = { name: "tom", age: 30 }
// this.state.nameList.push(newData)
// this.setState({
// nameList:this.state.nameList
// })
// 推荐做法
const newNameList = [...this.state.nameList]
newNameList.push({ name: "tom", age: 28 })
this.setState({
nameList: newNameList
})
}
addNum(index) {
const newNames = [...this.state.nameList]
const addNewAge = newNames[index].age += 1
this.setState({
age:addNewAge
})
}
}
export default App;
全局事件 events 兄弟之间的传递
- 需求 当我在这个组件中点击按钮
向上面的Home 组件传递一条数据过去
然后在Home组件中展示出来
解决方案:
需要下载一个第三方库 events --- > yarn add events
import React, { PureComponent } from 'react';
// 1.EventEmitter(事件发射)--> 这个是一个类
import { EventEmitter } from "events"
// 2.创建事件总线: event bus
// 像这样的文件你是可以单独的存在一个全局的文件中的
// 然后你再导入到你需要的文件中
const eventBus = new EventEmitter();
class Home extends PureComponent {
// 5. 要想监听的话 你要在组件的生命周期里面去监听
// componentDidMount(){} 要在挂载阶段去监听你的事件
// componentWillUnmount() {} 要在卸载过程去下载你的事件
componentDidMount() {
/**
* eventBus.addListener()方法里面是有2个参数的
*
* 第一个参数是:监听兄弟组件传递过来的事件
* 第二个参数是: 一个回调函数
*
* **/
// eventBus.addListener('sayHello',(...args)=>{
// console.log(args);
// })
eventBus.addListener('sayHello',this.handleSayHelloListenner)
}
componentWillUnmount() {
// 6. 当组件即将小时的时候 你应当清楚监听事件
// eventBus.removeListener()清除事件名称
eventBus.removeListener("sayHello",this.handleSayHelloListenner)
}
// 单独定义这个事件
handleSayHelloListenner(...args) {
console.log(...args);
}
render() {
return (
Home
)
}
}
// 需求 当我在这个组件中点击按钮
// 向上面的Home 组件传递一条数据过去
// 然后在Home组件中展示出来
// 解决方案:
// 需要下载一个第三方库 events --- > yarn add events
class Profile extends PureComponent {
render() {
return (
Profile
{/* 3.发射一个事件出去 */}
)
}
emitEvent() {
// 上面全局定义的一个eventBus对象 他new出来的有个事件叫emit
// 4. emit() 方法里面 第一个参数是:事件名称,第二个是: 传递的参数
eventBus.emit("sayHello", 'hello,world', 123322)
}
}
class App extends PureComponent {
render() {
return (
);
}
}
export default App;
事件总线 总结:
- 前端通过Context主要实现的是数据的共享, 但是在开发中如果有跨组件之间的时间传递,应该如何操作呢?
(1) 在Vue中我们可以通过Vue的实例,快速实现一个事件总线(eventBus),来完成操作
(2) 在React中,我们可以依赖一个使用较多的库events来完成对应的操作
(3) 我们可以通过npm 或者yarn来安装events
yarn add events
(4) events常用的API:
-- 创建EventEmitter对象: eventBus对象
const eventBus = new EventEmitter()
-- 发射事件: eventBus.emit("事件名称",参数列表)
eventBus.emit("sayHello", 'hello,world', 123322)
-- 监听事件:events.addListener("事件监听",监听函数)
eventBus.addListener('sayHello',this.handleSayHelloListenner)
-- 移除事件:eventBus.removeListener("事件监听",监听函数)
eventBus.removeListener(sayHello",this.handleSayHelloListenner)
如何使用ref
- 在React的开发模式中,通常项情况下不需要,也不建议直接去操作DOM原生,但是某些特殊情况,确实需要获取到DOM进行某些操作
(1) 管理焦点,文本选择或媒体播放
(2) 触发强制动画
(3) 继承第三方DOM库
- 如何创建refs来获取对应的DOM呢? 目前是有三种方式:
(1) 方式一: 传入字符串
-- 使用时通过this.refs. 传入的字符串格式获取对应的元素
import React, { PureComponent } from 'react';
class App extends PureComponent {
componentDidMount() {
// document.getElementById() // 一般不推荐
}
render() {
return (
{/* 1.方式一: ref=字符串/对象/函数 */}
React,Hello
);
}
changeText() {
// 2.
// console.log(this.refs.titleRef);
this.refs.titleRef.innerHTML = "哈哈哈哈"
}
}
export default App;
(2) 方式二: 传入一个对象
-- 对象是通过React.createRef() 方式创建出来的
-- 使用时获取到创建的对象其中有一个current属性就是对应的元素
// 1.导入一个 createRef
import React, { createRef, PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
// 2.第二步:
this.titleRef = createRef();
}
render() {
return (
{/* 3. 使用ref=对象(ref={this.titleRef}) */}
React,Hello
);
}
changeText() {
// 4.对象的使用方式:
console.log(this.titleRef); // {current:h2}
this.titleRef.current.innerHTML = "Hello,JavaScript"
}
}
export default App;
(3) 方式三:传入一个函数
-- 该函数会在DOM被挂载时进行回调, 这个函数会传入一个 元素对象,我们可以自己保存;
-- 使用时,直接拿到之前保存的元素对象即可
import React, { PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.titleRef = null
}
render() {
return (
{/* 1.传入一个函数基本语法:ref={arg =>this.titleRef = arg */}
this.titleRef = arg}>React,Hello
);
}
changeText() {
// 2.
console.log(this.titleRef);
this.titleRef.innerHTML = "Hello,TypeScript"
}
}
export default App;
refs 的类型
- ref 的值根据节点的类型而有所不同
-- 当ref属性用于HTML元素时.构造函数中使用React.createRef()创建的ref接收底层DOM元素作为DOM 元素作为其current属性
-- 当ref属性用于自定义class组件是,ref对象接收组价的挂载实例作为其current属性
-- 你不能在函数组件上使用ref属性.因为他们没有实的 - 函数式组件是没有实例的,所以无法通过ref获取他们的实例
-- 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素
-- 这个时候我们可以通过React.forwardRef,后面我们也会学习hooks中如何使用ref
认识受控组件
在React中,HTML表单的处理方式和普通的DOM元素不太一样,表单元素通常会保存在一些内部的state
比如下面的HTML表单元素
-- 这个处理方式是DOM默认的处理HTML表单的行为, 在用户点击提交时会提交到某个服务器中, 并且刷新页面
-- 在React中,并没有禁止这个行为,他依然是有效的
-- 但是尝尝情况下使用JavaScript函数来方便的处理表单提交,同时还可以访问影虎填写的表单数据
-- 实现这种效果的标准方式是使用 "受控组件"
非受控组件
import React, { PureComponent,createRef } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.usernameRef = createRef()
}
render() {
return (
);
}
handleSubmit(e) {
e.preventDefault()
console.log(this.usernameRef.current.value);
}
}
export default App;
认识高阶函数
认识高阶组件
高阶组件的英文名叫Higher-Order Components,简称HOC
官方的定义:高阶组件是参数为组件,返回值为新组件的函数
首先,高阶组件 本身不是一个组件,而是一个函数
其次,这个函数的参数是一个组件, 返回值也是一个组件
高阶组件的调用类似于这样:
const EnhancedComponent = higherOrderComonent(WrappedComponent)
Portals 的使用
在某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中,(默认都是挂载到id为root的DOM元素上的)
Portal 提供了一种将子节点渲染到存在父组件以外的DOM节点的优秀的案例
--第一个参数(child) 是任何可渲染的React子元素,例如一个元素,字符串或fragment;
-- 第二个参数是(container) 是一个DOM元素
ReactDOM.createPortal(child,container)
通常来讲,当你从组建的render方法返回一个元素时,该方法被挂载到DOM节点中离其最近的父节点:
然而,有时候将子元素插入到DOM节点中的不同位置也是有好处的
render() {
// React 挂载一个新的div, 并且把子元素渲染其中
return(
{this.props.children}
)
}
render() {
// React 并没有创建一个新的div 它只是把子元素渲染到'DOMNode'中
return ReactDOM.createPortal(
this.props.children,
domNode
)
}
Modal 组建案例
- 比如说,我们准备开发一个Modal组件, 他可以将他的子组件渲染到品目的中间位置:
fragment的使用
// 1.fragment的使用 导入Fragment,类似于小程序中的 block 标签
import React, { PureComponent, Fragment } from 'react';
export default class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0,
friends:[
{name:"huzhenchu",age:18,sex:"男"},
{name:"huzhenchu",age:18,sex:"男"},
{name:"huzhenchu",age:18,sex:"男"},
]
}
}
render() {
return (
// 2.Fragment标签,类似于小程序中的 block 标签
//
// 当前计数:{ this.state.count }
//
//
// 或者还可以这样写de
<>
当前计数:{this.state.count}
{
this.state.friends.map(item=> {
return (
//
// {item.name}
// {item.age}
//
// 或者这样写 但是你要写key的话,这种段语法 是不可以添加任何属性的
// 这种情况你必须要写
<>
{item.name}
{item.age}
>
)
})
}
>
);
}
addNum() {
this.setState({
count: this.state.count + 1
})
}
}
StrictMode:React
ReactDOM.render(
,
document.getElementById('root')
)
StrictMode 是一个用来突出显示应用程序中潜在问题的工具
-- 与Fragment一样,StrictMode不会渲染任何可见的UI;
--它为其后代元素触发额外的检查和警告
--严格模式检查仅在开发模式下运行;他们不会影响生产构建可以为应用程序的任何部分启用严格模式
--不会对Header和Footer组件运行严格模式检查
--但是,ComponentOne 和 ComponentTwo以及他们的所有后代元素都将进行检查
严格模式检查什么?
--1. 识别不安全的生命周期
--2. 使用过时的ref API
--3. 检查意外的副作用
--4. 使用废弃的findDOMMNode
组件化开发的CSS
- 局部CSS的样式
- 动态的CSS的书写
- 支持所有的CSS 特性
- 避免样式的全局污染
React 中的四种CSS样式的编写
1.1. 内联样式的写法
-- style 接收一个采用小驼峰命名的属性的JavaScript对象,而不是CSS字符串
-- 并且可以引用state中的状态来设置相关的样式
1.2. 内联样式的优点
-- 1. 内联样式,样式之间不会有冲突
-- 2. 可以动态获取当前state中的状态
1.3. 内联样式的缺点:
--写法上都需要使用驼峰标识
--某些样式没有提示
--大量的样式,代码混乱
--某些样式无法编写(比如伪类/伪元素)
- 使用普通的css来写,导入到各个JS 文件中
-- 缺点: 会覆盖和层叠其他文件中的css样式
-
CSS modules
(1) CSS modules 并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的.
--但是,如果在其他项目中使用,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules:true等.(2) React的脚手架已经内置了css modules 的配置
--.css/.less/.scss等样式文件都修改成.module.css/.module.less/.module.scss等(3) 这样写的话 就可以完美的解决层叠问题和样式覆盖问题的
(4) 优点 确实解决了局部的作用域问题,
(5) 缺点
--引用的类名,不能使用连接符比如(.home-title),在JavaScript中是不识别的
--所有的className都必须使用(style.classname)的形式来编写
--不方便动态来修改某些样式,依然需要使用内联样式的方式
CSS in JS
(1) "CSS-in-JS" 是一种模式,其中CSS由JavaScript生成而不是在外部定义的
(2) 注意此功能并不是React的一部分,而是由第三方库提供,React对样式如何定义并没有明确态度
(3) 事实上CSS-in-JS的模式就是一种将样式(CSS) 也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态,所以有被人称之为 All in JS-
认识styled-components
(1) CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套,函数定义,逻辑复用,动态修改状态..
(2) 依然CSS预处理器也是具备某些能力,但是获取动态状态依然是一个不好处理的点;
(3) 目前CSS-in-JS可以说是React最受欢迎的一种解决方案(4) 目前比较流行的CSS-in-JS 的库有哪些呢?
--styled-components
--emotion
--glamorous
(5) 目前可以说 styled-components依然是社区最流行的CSS-in-JS(6) 安装styled-components
yarn add styled-components
React 中 编写className的写法 React中添加Class :
- React在JSX给了我们开发者足够多的灵活性, 你可以像JavaScript代码一样,通过一些逻辑来决定时候添加某些class:
{/* 这些都是原生的React添加class 的方法 */}
我是标题一
{/* 这种写 如果title的后面或者 active的前面不加一个空格的话就会连到一起,这种写不是很方便 */}
我是标题二
{/* 这样的话比较清楚,为什么呢? 这个地方是可以写一个数组的 */}
我是标题三
这个时候我们就可以借助第三方库:classnames
-- 很明显,这个是一个用于动态添加className的一个库库的安装:
yarn add classname
classNames 库的具体用法:
import React, { PureComponent } from 'react';
/**
*
*
*
* 1. React在JSX给了我们开发者足够多的灵活性, 你可以像JavaScript代码一样,通过一些逻辑来决定时候添加某些class:
* 这些都是原生的React添加class 的方法
* 我是标题一
* 这种写 如果title的后面或者 active的前面不加一个空格的话就会连到一起,这种写不是很方便
* 我是标题二
*
* 这样的话比较清楚,为什么呢? 这个地方是可以写一个数组的
* 我是标题三
* 2. 这个时候我们就可以借助第三方库:classnames
* -- 很明显,这个是一个用于动态添加className的一个库
* 3. 库的安装命令:
* yarn add classname
*
* **/
// 1. 现下载安装 className库 yarn add classname
// 2. 导入
import classNames from "classname"
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isActive: true,
isBar: false
}
}
render() {
const { isActive, isBar } = this.state;
const errorClass = "error";
const warnClass = null; // 这个不会被加进去
const undiClass = undefined; // 这个不会被加进去
const zreo = 0; // 这个不会被加进去
const tenNum = 10 // 数字为真的时候会被加进去
return (
{/* 这些都是原生的React添加class 的方法 */}
我是标题一
{/* 这种写 如果title的后面或者 active的前面不加一个空格的话就会连到一起,这种写不是很方便 */}
我是标题二
{/* 这样的话比较清楚,为什么呢? 这个地方是可以写一个数组的 */}
我是标题三
{/* classnames库添加class 语法使用如下: */}
我是标题一
我是标题四
{/* 如果有些属性是必须要加的话,你就写在对象的外面,如下:"title" */}
我是标题五
{/* 可以跟变量 */}
我是标题六
我是标题七
我是标题四
);
}
}
export default App;
AntDesign 的使用
AntDesign 安装 使用 npm 或者 yarn 安装
npm install antd -save
或者
yarn add antd'Antd 是否有多余的代码逻辑添加进来呢?
不会的
认识craco
- 想要修改create-react-app 的默认配置可以使用:
yarn run eject 可以把信息全部展示出来,但是这种不太推荐
- 推进的修改 craco
- 安装craco 这个包
-- yarn add @craco/craco - 还要安装一个命令:
-- yarn add craco-less - 然后安装 craco-less 并修改 craco.config.js 文件如下。
- 安装craco 这个包
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
文件配置取别名:
const CracoLessPlugin = require('craco-less');
// 导入当前路径path 模块, 这个就是node_modules里面自带的一个模块路径
const path = require('path')
// __dirname:当前页面的路径,dir 是你要传入的一个路径 然后 path.resolve 做一个拼接
const resolve = dir => path.resolve(__dirname, dir)
// 修改主题颜色
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
// 修改主题颜色
lessOptions: {
modifyVars: { '@primary-color': '#f00' },
javascriptEnabled: true,
},
},
},
},
],
//webpack 别名的修改配置
webpack: {
alias: {
"@": resolve("src"), // @:这个意思就是代表当前路径所在的 src的这个路径
"component":resolve("src/component"), // component就是当前所在的路径 所在的src下面的component,如果以后写component就是找src/component路径了
}
}
};
axios 的网络请求
- Axios 的基本使用命令:
yarn add axios
React-transition-group介绍
React社区为我们提供了react-transition-group用来过渡完成动画
-
这个是需要安装一个插件的,安装命令:
npm 安装
npm install react-transition-group --save
yarn
yarn add react-transition-group
-
react-transition-group 主要包含四个组件:
-- Transition
该组件是一个和平台无关的组件(不一定要结合CSS);
一般是结合CSS来完成样式, 比较常用的是CSSTransition-- CSSTransition
在前端开发中,通常使用的是CSSTransition来完成过渡动画效果--SwitchTransition
两个组件显示和影藏切换时,使用该组件--TransitionGroup
将多个动画组件包裹在其中, 一般用于列表中元素的动画
Redux的核心理念-reducer
reducer 是一个纯函数
reducer做的事情就是将传入的state和action结合起来生成一个新的state
Redux的三大原则
--整个应用程序的state被储存在一个object tree中,并且这个object tree只储存在一个store中;
--Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
--单一的数据源可以让整个应用程序的state变得方便维护,追踪,修改State是只读的
--唯一修改state的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
--这样就确保了View或网络请求都不能直接修改state,他们只能通过action来描述自己想要如何修改state
--这样可以保证所有的修改都被集中化处理,并且按照严格的顺序执行,所以不需要担心race condition 的问题使用纯函数来执行修改
-- 通过reducer将旧state和actions联系在一起,并且返回一个新的State
--随着应用程序的复杂度增加, 我们可以将reduce拆分成多个小的reducers,分别操作不同state tree的一部分
--但是所有的reducer都应该是纯函数, 不能产生任何的副作用;redux三个核心的东西就是: reducer store actios
--把所有的state储存在store中 想要修改的话 必须要通过 actions 然后通过reducer来进行视图的更改, 但是所有的reducer都应该是纯函数,所有的数据应该只存在一个store中
React-router 路由
- 安装react-router会自动帮助我们安装react-router的依赖 命令如下:
yarn add react-router-dom
- react-router最主要的API是给我们提供的一些组件
(1) BrowserRouter或HashRouter
--Router中包含了对路径的改变的监听,并且将会相应的路径传递给子组件
--BrowserRouter使用history模式
--HashRouter使用hash模式
(2) Link和NavLink
--通常路径的跳转是使用Link组件,最终会被渲染成a 元素
--NavLink是在Link基础之上增加了一些样式属性
--to属性:LInk最重要的属性,用于设置跳转到的路径
(3) Route:
--Route用于路径的匹配
--path属性:用于设置匹配到的路径
--component属性:设置匹配到路径后,渲染组件
--exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件