直接声明:当属性不参与页面渲染的时候,只参与逻辑运算,可以将其设置为类属性,直接声明。
在 constructor 中声明 this.state:在 constructor 构造器中,声明组件私有数据,只要有 constructor构造器,就必须要有 super() 调用,也可以不使用constructor,直接声明state数据。
props :(单向数据流)一般用于父子组件,自上而下,父组件向子组件传值。
state :用于声明修改数据。
context :主要是应用在组件树之间的传值,外层的父组件负责Context对象提供数据,它内部的嵌套组件都可以使用Context取值,避免props层层传递的现象。创建context实例,使用provider包裹组件树,并提供数据,被它包裹的组件树就可以使用Provider提供的数据,达到共享数据的目的。
refs:是 React 提供给我们安全的访问 DOM 元素或者某个组件实例的句柄, 它是组件对象this的一个属性, 可以在组件模板中的标签添加ref属性, 然后组件中使用this.refs即可访问对应的DOM元素
state属性也是一种类属性,react中规定了使用this.setState修改数据,可以触发render函数,进行重新渲染页面。不使用this.setState,使用this.state…修改属性,这就是普通的class属性的修改,当然是可以的,而且这时拿到的数据还是最新的数据,就是不会渲染页面了。如果在一次执行中,只要有一次this.setState,那么就可以实现整个页面的渲染
setState({}, () => {})
**,React 会在数据更新后,立即执行回调函数。setState(state => { return {}})
,或this.setState((state)=>{})
此时回调函数中的参数 state 也是最新数据。 // 基本使用
// 1. 准备事件中心:它主要负责协调发布者和订阅者。它内部主要是存储订阅者注册的各种事件,而发布者则会通过事件中心调用这些事件,从而让订阅者收到消息。
let events = []; // 存储订阅者事件的数组
let eventsCenter = {
subscribe(func) {
events.push(func);
}, // subscribe 它是让订阅者注册事件的方法。
publish(info) {
// info 就是发布者提供的信息
events.forEach(event => event(info));
}, // publish 让发布者调用的。
};
// 2. 创建订阅者:订阅者一定先往事件中心注册事件,然后再发布。
eventsCenter.subscribe(data => {
console.log("订阅者1", data);
});
eventsCenter.subscribe(data => {
console.log("订阅者2", data);
});
eventsCenter.subscribe(data => {
console.log("订阅者3", data);
});
// 3. 发布消息
eventsCenter.publish("下课了");
- 虚拟 DOM 树 的diff算法对比:
- 同层对比标签名称、标签属性、标签内容是否一致;不会跨层级对比。
- 如果虚拟 DOM 出现跨层级移动,React 直接将旧层级删除,在新的节点下创建新的层级元素。不会复用旧的 DOM 层级。- 组件 的diff算法对比 (结合动态组件):
- 如果在重复 render() 生成虚拟 DOM 时,组件没有发生变化,此时组件就相当于固定的虚拟 DOM 树了,只是封装了一个组件而已。参照虚拟 DOM 树的diff算法对比规则。
- 如果组件是动态切换的,React 直接放弃对比,哪怕两个组件的结构是一样的,直接将消失的组件 DOM 移除,出现的组件再创建新的 DOM 节点。(谁出现就创建谁,谁消失就销毁谁)。- 列表元素 的diff算法对比 (结合 key):
- 没有 key 属性的时候,React 对比新旧虚拟 DOM 时,会按照元素的顺序对比元素内容是否发生变化,变化了就重新生成 DOM。
- 有 key 属性的时候,React 对比新旧虚拟 DOM 时,会将两个 key 值相同的元素进行对比,看看元素内容是否变化,变化了就重新生成 DOM。
虚拟 DOM 会进行 diff算法 的对比,从而发现哪些 DOM 更新了,根据变化了的虚拟 DOM 局部更新真实 DOM,避免频繁的操作真实 DOM,极大的提升了页面渲染的性能。
虚拟 DOM 本质上就是react提供的一个 JS 对象而已,React 通过对象来描述元素结构,这就是虚拟 DOM。它是 React 中唯一的一个性能优化钩子函数,如果项目性能没有问题,页面加载速度也比较快,这个钩子就不用使用了。
它这个钩子要求返回布尔值,返回 true,就会调用当前组件的 render(),生成虚拟 DOM;返回 false,就不会调用当前组件的 render(),减少一些 JS 的计算过程。
shouldComponentUpdate() 是在对新旧数据进行比较,发现数据改变,从而触发 render,组件渲染更新。
1. 什么情况下子组件需要 render,即 return true?
当子组件接收了父组件传递的 prop 数据时,父组件数据更新时,子组件也需要 render 更新。
对于组件本身的 state 来说,当调用 setState 的时候,数据发生变化,此时组件自身应该 render。
2. 什么情况下子组件不需要 render,即 return false?
当子组件没有接收任何父组件传递的 prop 数据时,当父组件数据更新时,子组件不需要 render。
对于组件本身的 state 来说,当调用 setState 的时候,数据并没有发生变化,此时组件自身也不应该render。
注意:PureComponent ,它是默认实现了 shouldComponentUpdate() 钩子,内部会根据 props 和 state 是否发生变化,做出浅层对比,从而让当前组件或子组件进行渲染优化,提升性能
对于基本数据类型 string/boolean/number,shouldComponentUpdate 可以很好的识别;对于引用数据类型 array/object,shouldComponentUpdate 识别不了对象内部属性或元素的变化,因为引用类型指向的都是指向的同一个对象的内存地址。解决 shouldComponentUpdate 正确识别对象/数组内部属性变化的唯一方式就是:基于旧对象生成新对象。
基于旧对象生成新对象的方式:
1. {...}展开运算符和 JSON 对象两个方法。弊端是对于内容复杂的对象来说,每次都将所有节点复制生成新节点,比较浪费内存和CPU性能。
2. 使用 Immutable不可变对象 生成新对象。
Context:它主要是应用在组件树之间的传值,外层的父组件负责 Context 对象提供数据,它内部的嵌套组件都可以使用 Context 取值,避免 props 层层传递的现象。
使用方式:
1. 创建上下文对象Context;
2. 利用 包裹组件树,并提供数据,被它包裹的组件树就可以使用 Provider 提供的数据。
3. 通过 Class.contextType(contextType 该属性是固定名称)这个类属性,获取上下文对象 Context 提供的数据。此属性可以让你使用 this.context 来获取最近 Context 上绑定的值。
4. 通过 this.context 这个属性获取值(context属性是固定的)。
PureComponent ,它是默认实现了 shouldComponentUpdate() 钩子,内部会根据 props 和 state 是否发生变化,做出浅层对比,从而让当前组件或子组件进行渲染优化,提升性能。
组件挂载阶段(初始渲染阶段):
1. constructor : 在执行组件初始化时执行,可以给组件初始化state,或者给点击事件bind绑定this;只会触发一次;
2. render : 根据组件模板编译成虚拟DOM,此时页面还没有渲染;
不要在 render 内调用 this.setState(),容易形成死循环;
render 由于会触发多次,只做数据渲染操作,比如结构 props、state。不要在此处发送请求或执行一些渲染无关的操作;
3. componentDidMount : 组件已经挂载完毕,此时已经可以看到页面了,虚拟DOM已被转化为真实DOM了。可以在该函数操作DOM。只执行一次。
组件初始化请求,放在 componentDidMount 钩子中即可。虽然 constructor 也可以发送请求,且执行时机比 componentDidMount 更早,但是react对于初始化请求的发送,显然是等到组件挂载完毕之后才正式发送请求的异步任务的。因此初始化请求不会放在constructor中。
组件更新阶段:
1. render : 三种情况会触发render函数的调用,a.父组件传递了新的Prop;b.子组件调用了setState()修改私有数据;c.forceUpdate()强制渲染。
2. componentDidUpdate : 新DOM更新完毕,此时可以获取最新的DOM结构。
以上两个钩子都是频繁执行的,只要props或state更新,它两就会执行。
组件销毁阶段:
componentWillUnmount : 做一些清理组件内容的操作,比如定时器、添加的订阅,监听的事件(addEventListener)。
使用** url 添加“?”拼接字符串**形式传值,目标组件使用 this.props.location.search 接收参数。
使用** url 动态传值**(路由配置中使用“:”设置参数类型),目标组件使用 this.props.location.params 接收参数。
使用** query 对象**传值,目标组件使用 this.props.location.query 接收参数。
使用** state 对象**传值,目标组件使用 this.props.location.state 接收参数。
还可以使用编程式导航跳转路由,使用 this.props.history.push() 传值。
注意:使用对象传值及编程式导航传值时,如果页面刷新,那么传递的值就会消失。
<Suspense
fallback={
<div className="centered">
<LoadingSpinner />
</div>
}
>
路由组件
</Suspense>
暴露配置:
yarn eject
安装 cross-env:
yarn add cross-env
在项目根目录下创建 .env ,.env.development ,.env.production ,.env.uat ,.env.sit:
.env
REACT_APP_ENV=development
.env.development
REACT_APP_ENV=development
.env.production
REACT_APP_ENV=production
.env.uat
REACT_APP_ENV=uat
.env.sit
REACT_APP_ENV=sit
配置 package.json :
"scripts": {
"serve": "cross-env REACT_APP_ENV=development node scripts/start.js",
"build": "cross-env REACT_APP_ENV=production node scripts/build.js",
"uat": "cross-env REACT_APP_ENV=uat node scripts/build.js",
"sit": "cross-env REACT_APP_ENV=sit node scripts/build.js"
},
项目中获取当前环境变量:
console.log(process.env.REACT_APP_ENV)
// auth.js
import { Navigate } from "react-router-dom";
export default function AuthConfig(props) {
let token = localStorage.getItem("token") || "";
if (token) {
// 登录了,给一级路由的element属性,传递目标组件
return props.target;
} else {
// 没有登录,给一级路由的element属性,传递Navigate
return <Navigate to={`/login?redirect=${props.targetURL}`} replace={true}></Navigate>;
}
}
import AuthConfig from "../utils/auth";
class RouterMap extends Component {
render() {
return (
<Routes>
{routes.map((route) => {
return (
<Route
key={route.name}
path={route.path}
element={
// route.component
route.meta.auth ? (
<AuthConfig
target={route.component}
targetURL={route.path}
></AuthConfig>
) : (
route.component
)
}
>
{route.children &&
route.children.map((subroute) =>
subroute.path ? (
<Route
key={subroute.name}
path={subroute.path}
element={subroute.component}
></Route>
) : (
<Route
key={subroute.name}
index
element={subroute.component}
></Route>
)
)}
</Route>
);
})}
</Routes>
);
}
}
底层原理不一样:
BrowserRoute 使用的是 H5 的 history API,不兼容 IE9 及以下版本;
HashRouter 使用的是 URL 的哈希值。
path 表现形式不一样:
BrowserRouter 的路径中没有 # ,例如:localhost:3000/demo/test;
HashRouter 的路径包含 # ,例如:localhost:3000/#/demo/test。
刷新后对路由state参数的影响:
BrowserRouter 没有任何影响,因为 state 保存在 history 对象中;
HashRouter 刷新后会导致路由 state 参数的丢失。
HashRouter 可以用于解决一些路径错误相关的问题。
异步 action 并不能修改 redux 数据,只是可以让我们发送额外的请求而已,最终请求结果的保存还是要依靠 dispatch({type: “”})的同步 action 完成。
import styles from './index.module.scss'
{
// 其他配置项......
"proxy": "http://localhost:5000"
}
const proxy = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
proxy('/api1', { // 当发起以 /api1 为前缀的请求时,触发该代理
target: 'http://localhost:5000', /* 要转发的地址 */
changeOrigin: true, /* 控制服务器接收的 Host 字段的值 */
// 将 /api1 前缀置为空
pathRewrite: {'^/api1': ''}
}),
// 第2、3、....、n个代理
)
}