概念
它是一个将数据渲染为 HTML 视图 的 js 库
原生 js 痛点
用 dom 的 API 去操作 dom,繁琐且效率低
用 js 直接操作 dom,浏览器会进行大量的回流和重绘
原生 js 没有组件化的编程方案,代码复用性低,对样式和结构也没办法拆解
React 特点
采用组件化模式,声明式编码,提高开发效率和组件复用性
在 React Native 中可以用 react 预发进行安卓、ios 移动端开发
使用虚拟 dom 和有些的 diffing 算法,尽量减少与真实 dom 的交互,提高性能
模块与组件、模块化与组件化
模块
- 一般就是指 js 文件
- 为什么要拆成模块: 随着业务逻辑的增加,代码越来越复杂
- 作用: 复用 js,简化 js 的编写,提高 js 的工作效率
组件
- 用来实现局部功能效果的代码和资源的集合
- 为什么使用组件: 因为一个界面的功能很复杂,需要拆分
- 作用: 复用编码,简化项目编码,提高运行效率
模块化
当应用的 js 都是已模块的方式来编写的时候,那么这个应用就是模块化的应用
组件化
当应用是已多组件的方式实现的,那么这个应用就是组件化的应用
组件的使用
React 通过组件的思想,将界面拆分成一个个可复用的模块,每一个模块就是一个 React 组件。一个 React 应用由若干组件组合而成,一个复杂组件也可以由若干简单组件组合而成
创建组件方式
组件是由元素构成的。元素数据结构是普通对象,而组件数据结构是类或纯函数
定义组件的两个要求:
- 组件名称必须以大写字母开头
- 组件的返回值只能有一个根元素
函数组件
- 就是一个函数
- 没有生命周期函数
- 不能使用 state,只会接收一个 props 形参,并且直接使用 props 参数不需要 this
- 无状态组件就是一个简单的视图函数,没有业务逻辑更纯粹的展示 UI
代码示例:
function Welcome() {
console.log(this) // undefined
// bable 中开启严格模式
return (
<div>无状态组件 ( 适用于 简单 组件的定义 无state 状态 ))</div>
)
}
类式组件
- 是一个 class 类,继承了类的组件是类式组件,继承自 React.Component 类
- 类式组件必须拥有继承 extends React.Component 和 render 属性
- 可以使用 state 和 props,并且都是使用 this 来调用 this.state 或 this.props
- 拥有生命周期函数
- 类式组件可以使用生命周期可以在里面写业务逻辑,可以在里面做任何事情
代码示例:
class Welcome extends React.Component {
render() {
console.log(this) // Welcome组件的实例对象
return (
<div>类式组件 ( 适用于 复杂 组件的定义 有state( 状态 ))</div>
)
}
}
事件处理
- 通过 onXxxx 属性指定事件处理函数(小驼峰形式)
- 通过 event.target 可以得到发生事件的 Dom 元素
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
组件的三大属性
state
- state 是组件实例对象最重要的属性,必须是对象的形式
- 组件被称为状态机,通过更改 state 的值来达到更新页面显示(重新渲染组件)
- 组件 render 中的 this 指的是组件实例对象
- 状态数据不能直接赋值,需要用 setState()
- 组件自定义方法中的 this 为 undefined, 怎么解决?
将自定义函数改为表达式+箭头函数的形式(推荐)
在构造器中用 bind()强制绑定 this
import React, { Fragment } from "react";
class Welcome extends React.Component {
constructor(props) {
// constructor 调用次数 1 页面初始化
super(props);
this.state = {
isHot: false,
};
}
change() {
// 状态的 (state) 不可直接修改,必须痛过 this.setState 方法
this.setState({ isHot: !this.state.isHot });
}
render() {
// render调用次数 1 + n 1: 页面初始化执行 n: 状态更新次数
const {
state: { isHot },
change,
} = this;
return (
<Fragment>
<div onClick={change.bind(this)}>
今天的天气{isHot ? "凉爽" : "炎热"}
</div>
</Fragment>
);
}
}
export default Welcome;
精简版:
import React, { Fragment } from "react";
class Welcome extends React.Component {
state = {
isHot: false,
num: 0,
};
change = () => {
//结果还是之前的,而不是+1之后的
// this.setState是异步的 在你调用了this.setState后在他的下面输出他的结果还是没变的状态
this.setState({ isHot: !this.state.isHot, num: this.state.num + 1 });
console.log(this.state.num, "执行1");
};
addNum = () => {
this.setState((data) => {
// this.setState的第一个参数可以是一个对象,也可以是一个函数返回一个对象,函数的参数是上一次的state
console.log(data);
return { num: data.num + 1 };
});
};
reduce = () => {
//this.setState的第二个参数是它的回调函数,在前面重新给state赋值后执行
this.setState({ num: this.state.num - 1 }, () => {
console.log(this.state.num);
});
};
render() {
console.log(this.state.num, "执行2");
const {
state: { isHot },
change,
addNum,
reduce,
} = this;
return (
<Fragment>
<div onClick={change}>今天的天气{isHot ? "凉爽" : "炎热"}</div>
<button onClick={addNum}>加➕</button>
<button onClick={reduce}>减➖</button>
</Fragment>
);
}
}
export default Welcome;
props
props 就是在调用组件的时候在组件中添加属性传到组件内部去使用
import React, { Fragment } from "react";
import PropTypes from "prop-types";
class Person extends React.Component {
// 类型检测
static propTypes = {
// array : 数组类型
// bool : 布尔类型
// func : 函数
// number : 数字
// object : 对象
// string : 字符串
// symbol : ES6新增的symbol类型
name: PropTypes.string.isRequired, // 必须传递字符串类型
sex: PropTypes.string, // 字符串类型
age: PropTypes.number, // 字符串类型
};
// 默认值
static defaultProps = {
sex: "女",
age: 18,
};
render() {
// props 是只读的,不可修改
const { name, age, sex } = this.props;
return (
<Fragment>
<div>姓名: {name}</div>
<div>年龄: {age}</div>
<div>性别: {sex}</div>
</Fragment>
);
}
}
class Welcome extends React.Component {
render() {
const a = { name: "猪猪" };
const b = { name: "粥粥", age: 23, sex: "男" };
return (
<Fragment>
<Person {...a} />
<Person {...b} />
</Fragment>
);
}
}
export default Welcome;
refs
refs 是组件实例对象中的属性,它专门用来收集那些打了 ref 标签的 dom 元 素
import React, { Fragment, createRef } from "react";
class Welcome extends React.Component {
myRef = createRef();
showData = () => {
// 不推荐使用 字符串 形的 ref, (效率不高)
const { input1 } = this.refs;
alert(input1.value);
};
showData2 = () => {
// 回调形式的 ref
// 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素
const { input2 } = this;
alert(input2.value);
};
showData3 = () => {
// React.createRef (推荐使用)
// 调用后可以返回一个容器,该容器可以存储被 ref 所表标识的节点, 该容器是 “专人专用” 的
const { value } = this.myRef.current;
alert(value);
};
render() {
const { showData, showData2, myRef, showData3 } = this;
return (
<Fragment>
<input ref="input1" placeholder="点击按钮提示" />
<button onClick={showData}>点击提示</button>
<input
ref={(current) => (this.input2 = current)}
onBlur={showData2}
placeholder="失去焦点提示"
/>
<input ref={myRef} onBlur={showData3} placeholder="失去焦点提示" />
</Fragment>
);
}
}
export default Welcome;
高阶函数和函数柯里化
高阶函数:如果一个函数符合下面的 2 个规范的任何一个,那么改函数就是高阶函数
- 若 A 函数,接受的参数是一个函数,那么 A 就是高阶函数
- 若 A 函数, 调用的返回值依然是一个函数,那么 A 就是高阶函数
常见的高阶函数: Promise map setTimeOut
函数柯里化:通过函数的回调继续返回函数的方式, 实现多次接受函数最后统一处理的函数编码形式
import React, { Fragment } from "react";
class Welcome extends React.Component {
state = {
name: "",
age: "",
};
save = (type) => {
return (e) => this.setState({ [type]: e.target.value });
};
render() {
return (
<Fragment>
<input onChange={this.save("name")} />
<input onChange={this.save("age")} />
</Fragment>
);
}
}
export default Welcome;
组件的生命周期
旧的生命周期 和 新的生命周期区别
旧的生命周期即将废弃 3 个勾子函数:
componentWillMount
componentWillReceiveProps
componentWillUpdate
新的生命周期新增 2 个勾子函数
getDerivedStateFromProps
getSnapshotBeforeUpdate
旧版本生命周期
生命周期的三个阶段
- 初始化阶段:由 ReactDom.render () 触发 - - - 初次渲染
constructor ()
componentWillMount ()
render ()
componentDidMount () 常用 - 更新阶段:由组件内部的 this.setState () 或 render () 触发
componentWillReceiveProps ()
shouldComponentUpdate ()
componentWillUpdate ()
render () 必用
componentDidUpdate () - 卸载阶段: 由 ReactDom.unmountComponentAtNode () 触发
componentWillUnmount () 常用
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
class Demo extends React.Component {
componentWillReceiveProps(props) {
console.log(props);
}
render() {
return <div>{this.props.conut}</div>;
}
}
class Welcome extends React.Component {
// 挂载 constructor componentWillMount render componentDidMount
// 构造器
constructor(prpos) {
console.log("第 1 执行");
super(prpos);
this.state = {
conut: 0,
num: 1,
};
this.num = 1;
}
// 组件挂将要挂在
componentWillMount() {
console.log("第 2 执行");
}
// 组件挂在完毕
componentDidMount() {
console.log("第 4 执行");
}
// 组件更新 componentWillReceiveProps shouldComponentUpdate render componentWillUpdate componentDidUpdate
// 控制组件是否被更新
shouldComponentUpdate() {
console.log("更新 1 执行");
const { conut } = this.state;
if (conut > 3) {
return false;
}
return true;
}
// 组件将要更新
componentWillUpdate() {
console.log("更新 2 执行");
}
// 组件更新完毕
componentDidUpdate() {
console.log("更新 4 执行");
}
// 组件将要卸载执行
componentWillUnmount() {
console.log("卸载 1 执行");
}
// 初始渲染 状态更新 执行
render() {
console.log("第 3 执行", "更新 3 执行");
const {
state: { conut },
} = this;
return (
<Fragment>
<h2>当前求和为:{conut}</h2>
<button onClick={() => this.setState({ conut: conut + 1 })}>
点我 + 1
</button>
<button
onClick={() => {
// 强制更新
this.state.conut += 1;
this.forceUpdate();
}}
>
强制更新
</button>
<Demo conut={conut} />
<button
onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}}
>
卸载组件
</button>
</Fragment>
);
}
}
export default Welcome;
新版本生命周期
生命周期的三个阶段
- 初始化阶段:由 ReactDom.render () 触发 - - - 初次渲染
constructor ()
getDerivedStateFromProps ()
render ()
componentDidMount () 常用 - 更新阶段:由组件内部的 this.setState () 或 render () 触发
getDerivedStateFromProps ()
shouldComponentUpdate ()
render () 必用
getSnapshotBeforeUpdate ()
componentDidUpdate () - 卸载阶段: 由 ReactDom.unmountComponentAtNode () 触发
componentWillUnmount () 常用
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
class Welcome extends React.Component {
// 挂载 constructor getDerivedStateFromProps render componentDidMount
// 构造器
constructor(prpos) {
console.log("第 1 执行");
super(prpos);
this.state = {
conut: 0,
num: 1,
};
this.num = 1;
}
static getDerivedStateFromProps(props, state) {
console.log("第 2 执行", "更新 1 执行");
return null;
}
// 组件挂在完毕
componentDidMount() {
console.log("第 4 执行");
}
// 组件更新 getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate
// 控制组件是否被更新
shouldComponentUpdate() {
console.log("更新 2 执行");
const { conut } = this.state;
if (conut > 3) {
return false;
}
return true;
}
getSnapshotBeforeUpdate() {
console.log("更新 4 执行");
return null;
}
// 组件更新完毕
componentDidUpdate() {
console.log("更新 5 执行");
}
// 组件卸载 componentWillUnmount
// 组件将要卸载执行
componentWillUnmount() {
console.log("卸载 1 执行");
}
// 初始渲染 状态更新 执行
render() {
console.log("第 3 执行", "更新 3 执行");
const {
state: { conut },
} = this;
return (
<Fragment>
<h2>当前求和为:{conut}</h2>
<button onClick={() => this.setState({ conut: conut + 1 })}>
点我 + 1
</button>
<button
onClick={() => {
// 强制更新
this.state.conut += 1;
this.forceUpdate();
}}
>
强制更新
</button>
<button
onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}}
>
卸载组件
</button>
</Fragment>
);
}
}
export default Welcome;
diff 算法
计算出虚拟 DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面
react/vue 中的 key 有什么作用: 当状态中的数据发生改变时,react 会根据【新数据】生成【新虚拟 DOM】,随后 react 会进行【新虚拟 DOM】和【旧虚拟 DOM】的 diff 算法比较,具体的比较规则如下:
- 若【旧 DOM】中找到了与【新 DOM】相同的 key,则会进一步判断两者的内容是否相同,如果也一样,则直接使用之前的真实 DOM,如果内容不一样,则会生成新的真实 DOM,替换掉原先的真实 DOM
- 若【旧 DOM】中没找到与【新 DOM】相同的 key,则直接生成新的真实 DOM,然后渲染到页面
用 index 作为 key 可能引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实 DOM 更新,造成效率低下
- 如果结构中还包含输入类的 dom,会产生错误 dom 更新,出现界面异常
开发中如何选择 key:
5. 最好选中标签的唯一标识 id、手机号等
6. 如果只是简单的展示数据,用 index 也是可以的
脚手架
安装脚手架
使用 create-react-app(脚手架工具)创建一个初始化项目
- 下载脚手架工具:npm i -g create-react-app
- 创建应用项目:create-react-app my-app
- 运行应用:cd my-app(进入应用文件夹),npm start(启动应用)
React 脚手架配置代理
方法一
在 package.json 中追加如下配置
"proxy":"http://localhost:5000"
1、优点:配置简单,前端请求资源可以不加任何前缀
2、缺点:不能配置多个代理(如果请求的不同服务器就不行)
3、工作方式:当请求了自身 3000 端口不存在的资源时,那么会转发给 5000 端口(优先会匹配自身的资源,如果自己有就不会请求 5000 端口了)
方法二
在 src 下创建配置文件:src/setupProxy.js
const proxy = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
proxy("/api", {
target: "http://localhost:4000/",
changeOrigin: true,
pathRewrite: { "^/api": "" },
}),
proxy("/api2", {
target: "http://localhost:5000/",
changeOrigin: true,
pathRewrite: { "^/api2": "" },
})
);
};
1、优点:可以配置多个代理,可以灵活控制请求是否走代理
2、缺点:配置繁琐,前端请求资源时必须加前缀
消息订阅-发布机制
原先 react 传递数据基本用的是 props,而且只能父组件传给子组件,如果子组件要传数据给父组件,只能先父组件传一个函数给子组件,子组件再调用该方法,把数据作为形参传给父组件,那考虑一个事情,兄弟间组件要如何传递数据呢?这就要引出下面这个消息订阅-发布机制
npm install pubsub-js --save
使用:
- 先引入:import PubSub from “pubsub-js”
- 传递数据方发布:PubSub.publish(‘消息名’,data)
- 要接收数据方订阅:PubSub.subscribe(‘消息名’,(data)=>{ console.log(data) })
import React, { Fragment } from "react";
import PubSub from "pubsub-js";
class Demo extends React.Component {
render() {
return (
<button
onClick={() => {
PubSub.publish("showNum", { num: 1 });
}}
>
点击
</button>
);
}
}
class Content extends React.Component {
state = {
num: 0,
};
componentDidMount() {
this.token = PubSub.subscribe("showNum", (_, setObjs) => {
this.setState(setObjs);
});
}
componentWillUnmount() {
PubSub.unsubscribe(this.token);
}
render() {
const { num } = this.state;
return <div>{num}</div>;
}
}
class App extends React.Component {
render() {
return (
<Fragment>
<Demo />
<Content />
</Fragment>
);
}
}
export default App;
redux
学习文档
- 英文文档: https://redux.js.org/
- 中文文档: https://redux.org.cn/
- GitHub 地址: https://github.com/reactis/redux
redux 是什么
- 它是专门做状态管理的 js 库,不是 react 插件库
- 它可以用在 angular、vue、react 等项目中,但与 react 配合用到最多
- 作用:集中式管理 react 应用中多个组件共享的状态
什么情况下需要使用它
- 某个组件的状态需要让其他组件也能拿到
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用,如果不用比较吃力,就可以使用
redux 的工作流程
redux 的三个核心概念
action
- 动作的对象
- 包含 2 个属性
- 标识属性,值为字符串,唯—,必要属性
- 数据属性,值类型任意,可选属性
- 栗子:type: { ‘ADD’, data : { name: “tom”, age:18} }
reducer
- 用于初始化状态、加工状态
- 加工时,根据旧的 state 和 action,产生新的 state 的纯函数
store
- 将 state、 action、 reducer 联系在一起的对象