一、核心设计
React Fiber
React Fiber 是 React 16 引入的一种新的协调引擎,旨在提高 React 应用的性能和响应能力。
Fiber 通过增量渲染、可中断与恢复、链表结构和优先级调度等机制,使得 React 可以更灵活地处理大量更新和复杂组件树。
工作原理:
调度:Fiber 引入了新的调度机制,允许 React 根据任务的优先级来调度任务。React 会根据任务的紧急程度将任务放入不同的队列中,并按照队列的顺序执行任务。
渲染:在渲染阶段,React 会遍历组件树,并构建一个 Fiber 树。Fiber 树中的每个节点代表一个组件,并包含组件的状态、属性等信息。
更新:当组件的状态或属性发生变化时,React 会触发更新。Fiber 会根据变化的类型和优先级来决定如何更新组件。
提交:在更新阶段完成后,React 会将 Fiber 树转换为实际的 DOM 树,并提交给浏览器进行渲染。
React虚拟DOM
React提出的一种解决方案,它是一个轻量级的JavaScript对象,用来描述真实DOM的结构和属性。
React通过比较虚拟DOM的差异,计算出需要更新的部分,然后再将这些部分更新到真实DOM上。
React虚拟DOM的原理是:
1. 首先,React将组件的状态和属性传入组件的render方法,得到一个虚拟DOM树。
2. 当组件的状态或属性发生变化时,React会再次调用render方法得到新的虚拟DOM树。
3. React会将新旧两棵虚拟DOM树进行比较,得到它们的不同之处。
4. React会将这些不同之处记录下来,然后批量的更新到真实的DOM树上。
React通过虚拟DOM树的比较,避免了直接操作真实DOM树带来的性能问题,因为直接操作真实DOM树会带来大量的重排和重绘,而React的虚拟DOM树的比较和更新是基于JavaScript对象进行的,不会导致页面的重排和重绘。
总结起来,React虚拟DOM的原理就是:通过比较虚拟DOM树的不同,批量的更新真实的DOM树,从而提高页面的性能。
1、框架设计原因:render函数无法绑定更新的数据,会全量生成渲染dom,开销比较高;
2、跨平台
React Diff算法
二、组件和基础
组件生成方式(命令式)
import React from 'react';
import ReactDOM from 'react-dom';
// 创建一个React元素
const myDiv = React.createElement('div', { id: 'myDiv', className: 'example' }, 'Hello World');
// 获取DOM节点
const container = document.getElementById('root');
// 渲染React元素到DOM节点
ReactDOM.render(myDiv, container);
组件的生成方式(声明式)
import React, { Component }from "react";
//组件名首字母大写
//类生成方式
class Header extends Component {
render() {
return <div>微博头部</div>;
}
}
//函数组件:没有生命周期,不能定义自己的状态()
//ES5生成方式
function Main() {
return <main>微博的内容</main>;
}
//ES6生成方式
const Footer = () => {
return <footer>微博底部</footer>;
};
class App extends Component {
render() {
return (
<>
{/* Fragment是一个空的占位标签 */}
<Header />
<Main />
<Footer />
</>
);
}
}
export default App;
受控组件
//内容可以由我们自己来控制的组件,必须要有value和onChange
class App extends Component {
state = {
valueText: 1
}
handleChange = (e) => {
this.setState({
valueText: e.target.value, //输入的值
});
}
handleClick = () => {
console.log(this.state.valueText);
}
render() {
return (
<>
<input
type="text"
value={this.state.valueText}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>btn</button>
</>
<p>输入的值是:{this.state.valueText}</p> //实现双向绑定效果
);
}
}
//函数组件 useState
const setUserName = (e) => {
setUserRealName(e.target.value)
}
非受控组件
//解构createRef,创建Refs并通过ref属性联系到React组件。Refs通常当组件被创建时被分配给实例变量,这样它们就能在组件中被引用。
import React, { Component, createRef } from "react";
class App extends Component {
num = createRef(); //current 属性是唯一可用的属性
handleClick2 = (ipt) => {
console.log(this.num.current.value);
}
render() {
return (
<>
<input type="text" ref={this.num} />
<button onClick={this.handleClick2}>btn</button>
</>
);
}
}
样式和类
1、组件中的内联样式
class Header extends React.Component {
render() {
return (<header style={{color:"red"}}>这是头</header>) //外层{}为jsx语法,内层{}为对象写法
}
}
2、直接导入css
import "XXX.css"; //导入定义过的css文件
const Main = ()=>{
return (<main className="orange big">这是身体</main>) //class为关键字,必须使用className
}
3、不同的条件添加不同的样式-使用classnames这个包
//下载安装classnames包
$npm i classnames
//引入classnames包
import classNames from "classNames/bind"
//引入CSS文件
import styles from './classNames.css'
let cx = classNames.bind(styles);
function Footer() {
let className = cx({
blue: true,
red: false
}) ;
return <footer className={className}>这是脚</footer>;
}
4、在js中写css改变样式
//安装包
$npm i styled-components
//新建含有css的js文件,导入模块并导出样式
import styled from "styled-components";
const Pstyled = styled.h1` //h1为标签名,后面接模板字符串
color: red;
font-size: ${(props) => props.size + "px"}; `; //可以通过props传值
export { Pstyled };
//组件中使用
import React, { Component } from "react";
import { Pstyled } from "./scc-in-js.js";
class App extends Component {
render() {
return (
<>
<Pstyled size="60">styled-components</Pstyled>
</>
);
}
}
export default App;
state-状态
1、通过申明式地定义state
class App extends Component {
state = {
num: 10,
}
render() {
const { num } = this.state; //解构state中的num
return (
<><h1>
num - {num}
</h1></>
)
}
}
2、通过构造函数的构造器constructor来定义state,当我们去实例化这个类的时候,它是会自动执行的
class App extends Component {
constructor() {
// 用于继承父类,只有调用过super之后才能使用this
super();
this.state = {
num: 10,
}
}
render() {
return (
<>
<p>
num - {this.state.num}
</p>
</>
)
}
}
! setState在合成事件是异步的
! setState在生命周期里是异步的
! setState在定时器里面是同步的
! setState在原生js里面是同步的
//setState有两种写法
//第一种是里面写对象,允许setState传第二个参数,是回调函数
this.setState( //需要修正this的指向
{
count: 10,
},
() => {
console.log(this.state.count);
}
);
//第二种方式是里面写函数, 可以接收一个参数(任意形参),表示前一次的state,允许setState传第二个参数,是回调函数
this.setState( //需要修正this的指向
(prevState) => {
return {
list: prevState.list.concat(item), //list: [...prevState.list, item],
};
},
() => {
console.log(this.state.item);
}
);
Portal(改变元素在DOM结构中的位置)
三、类组件生命周期
React组件的生命周期分为三个阶段:挂载阶段、更新阶段和卸载阶段
挂载阶段包括以下方法:
- constructor:组件被创建时调用,用于初始化状态和绑定事件处理函数。
- getDerivedStateFromProps:在组件挂载之前和更新时调用,用于根据props更新state。
- render:根据props和state渲染组件。
- componentDidMount:组件挂载后调用,用于进行异步操作和DOM操作。
更新阶段包括以下方法:
- getDerivedStateFromProps:在组件更新时调用,用于根据props更新state。
- shouldComponentUpdate:在组件更新前调用,用于判断是否需要重新渲染组件。
- render:根据props和state渲染组件。
- componentDidUpdate:在组件更新后调用,用于进行异步操作和DOM操作。
卸载阶段包括以下方法:
- componentWillUnmount:在组件卸载前调用,用于清理定时器、取消订阅等操作。
react更新触发生命周期
1. componentWillReceiveProps(nextProps)
当父组件接收到新的props时,会触发该生命周期方法。子组件的改变可能会导致父组件的props发生变化,从而触发该方法。
2. shouldComponentUpdate(nextProps, nextState)
当父组件的props或state发生改变时,会触发该方法。该方法用于判断是否需要重新渲染组件。如果返回false,组件将不会更新。
3. componentWillUpdate(nextProps, nextState)
在组件更新之前调用,可以在此方法中进行一些准备工作或进行一些副作用操作。
4. render()
无论父组件的props或state是否发生改变,都会触发render方法重新渲染父组件。
5. componentDidUpdate(prevProps, prevState)
在组件更新之后调用,可以在此方法中进行一些副作用操作,比如更新DOM或发送网络请求。
需要注意的是,以上生命周期方法中的一些在未来版本的React中可能会被废弃或改变。因此,建议在使用时查看React官方文档以获取最新信息。
类组件的生命周期-旧版
初始化
// 当这个类被实例化的时候就会自动执行,最先执行,并且只执行一次
constructor(props) { // 当props的值需要作为state的初始值的时候
super(props); //super必须写在最前面
this.state = {
count: 10,
};
this.ipt = createRef(); //非受控组件传值
}
挂载阶段
// componentWillMount是被废弃了, 改名成了UNSAFE_componentWillMount
UNSAFE_componentWillMount() {
console.log("componentWillMount");
// componentWillMount不能做数据请求
// 因为fiber算法的原因,可能导致这个生命周期被执行多次
}
// render本身就是一个生命周期,每次数据改变都重新渲染
render() {
console.log("render");
return (
<>
<h3>老版生命周期</h3>
<p>count: {this.state.count}</p>
</>
);
}
// 组件挂载完成之后
componentDidMount() {
console.log("componentDidMount");
this.setState({
count: 20,
});
}
更新阶段
// 被废弃了, 因为现在由更好的生命周期来代替
// 当父组件传递的自定义属性发生改变时就会触发
UNSAFE_componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
//决定子组件是否重新渲染
// 必须要返回true或者false, 通过返回true或者false来控制是否要重新渲染当前组件
// 可以接收两个参数,新的props和新的state,旧的还是this.props.xxx
// PureComponent 纯组件 用于代替Component
shouldComponentUpdate(nextProps, nextState) {
return nextState.count !== this.state.count;
}
// 被弃用
UNSAFE_componentWillUpdate() {
console.log("componentWillUpdate");
}
// 更新结束
// 不能在这里更新数据
componentDidUpdate() {
console.log("componentDidUpdate");
}
卸载阶段
//组件将要销毁
componentWillUnmount() {
// 定时器,事件监听,websocket, 插件等
console.log("componentWillUnmount");
}
类组件的生命周期-新版
getDerivedStateFromProps
// 根据props的值去获取一个新的state
// 它前面要有一个static关键字
// 必须要return 对象或者null 如果null表示啥也不做,如果是对象就会合并之前的state
getSnapshotBeforeUpdate
// 生成一个快照在更新之前
// 拿到更新前的状态给更新以后用
getSnapshotBeforeUpdate(prevProps, prevState) {
// 快照就是某一条记录或者某一个值,return出来被销毁周期接收
return prevState;
}
// componentDidUpdate第三个参数就是getSnapshotBeforeUpdate里面return的那个值
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot);
}
四、传参与通信
props传值检测
//安装包prop-types
$ npm i prop-types -S
//解构
import { number, string } from "prop-types";
//类组件检测
class Child extends Component {
static propTypes = {
count: number,
msg: string,
};
render() {
return <div>child - {this.props.count}</div>;
}
}
//函数组件检测
const Child = (props) => {
return <div>child - {props.count}</div>;
};
Child.propTypes = {
count: number,
msg: string,
};
父组件向子组件通信:使用 props
父组件 App.js:
import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";
export default class App extends Component{
render(){
return(
<div>
<Sub title = "今年过节不收礼" />
</div>
)
}
}
子组件 SubComponent.js:
import React from "react";
const Sub = (props) => {
return(
<h1>
{ props.title }
</h1>
)
}
export default Sub;
子组件向父组件通信:使用 props 回调
SubComponent.js代码:
import React from "react";
const Sub = (props) => {
const cb = (msg) => {
return () => {
props.callback(msg)
}
}
return(
<div>
<button onClick = { cb("我们通信吧") }>点击我</button>
</div>
)
}
export default Sub;
App.js代码:
import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";
export default class App extends Component{
callback(msg){
console.log(msg);
}
render(){
return(
<div>
<Sub callback = { this.callback.bind(this) } />
</div>
)
}
}
兄弟间的传参
1:子组件1中的事件去触发定义在父组件中的自定义事件,并传入子组件中的变量
class Child1 extends Component {
state = {
count: 10,
}
change = () => { //去触发父组件中定义的事件
this.props.onchange(this.state.count)
}
render() {
return (
<>
<div>{this.state.count}</div>
<button onClick={this.change}>按钮</button>
</>
);
}
}
2:父组件被子组件1的事件触发自己定义的事件,改变自己组件中的变量的值
class App extends React.Component {
state = { count: 5 }
Change = (count) => {
this.setState({ count: count })
}
render() {
return (
<>
<Child1 onchange={this.Change}></Child1> //上面的onchange为此处的事件
<p>****************************************************************</p>
<Child2 count={this.state.count}></Child2>
</>
)
}
}
3:子组件2用props去接收数据
//函数组件
const Child2 = (props) => {
return <p>{props.count}</p>
}
//类组件
class Done extends Component {
render() {
return (
<>
<p>{this.props.msg}</p> //接收props数据
</>
);
}
}
跨级组件间通信:使用 context 对象
App.js代码:
import React, { Component } from 'react';
import PropTypes from "prop-types";
import Sub from "./Sub";
import "./App.css";
export default class App extends Component{
// 父组件声明自己支持 context
static childContextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
// 父组件提供一个函数,用来返回相应的 context 对象
getChildContext(){
return{
color:"red",
callback:this.callback.bind(this)
}
}
callback(msg){
console.log(msg)
}
render(){
return(
<div>
<Sub></Sub>
</div>
);
}
}
Sub.js代码:
import React from "react";
import SubSub from "./SubSub";
const Sub = (props) =>{
return(
<div>
<SubSub />
</div>
);
}
export default Sub;
SubSub.js代码:
import React,{ Component } from "react";
import PropTypes from "prop-types";
export default class SubSub extends Component{
// 子组件声明自己需要使用 context
static contextTypes = {
color:PropTypes.string,
callback:PropTypes.func,
}
render(){
const style = { color:this.context.color }
const cb = (msg) => {
return () => {
this.context.callback(msg);
}
}
return(
<div style = { style }>
SUBSUB
<button onClick = { cb("我胡汉三又回来了!") }>点击我</button>
</div>
);
}
}
//解构出 createContext 函数
import React, { Component, createContext } from "react";
// Provider提供者,依赖
// Consumer消费者,注入
const { Provider, Consumer } = createContext();
//用Provider标签包裹父组件,Provider还必须要由一个属性value用来传参,可以传变量或者函数
render() {
return (
<Provider value={{ num: this.state.num }}>
<p>这是父组件</p>
<Child1 />
</Provider>
);
}
//子组件用Consumer去接收,里面写一个函数,参数为Value
<Consumer>
{(value) => (
<>
<button onClick={value.fn1}>-</button>
<span>{value.num}</span>
<button onClick={value.fn2}>+</button>
</>
)}
</Consumer>
父组件调用子组件的方法
在 React 中,可以使用 `ref` 来获取子组件的实例,并调用其方法。下面是一个示例:假设有一个子组件 `Child`,其中有一个 `handleClick` 方法:
class Child extends React.Component {
handleClick() {
console.log('Child clicked')
}
render() {
return <button onClick={this.handleClick}>Child button</button>
}
}
现在我们想要在父组件中调用 `Child` 组件的 `handleClick` 方法。我们可以在父组件中创建一个 `ref`,在 `Child` 组件上引用这个 `ref`,然后就可以在父组件中调用 `Child` 组件的 `handleClick` 方法了。
class Parent extends React.Component {
childRef = React.createRef()
handleClick() {
this.childRef.current.handleClick()
}
render() {
return (
<div>
<Child ref={this.childRef} />
<button onClick={() => this.handleClick()}>Call child method</button>
</div>
)
}
}
在上面的代码中,我们首先创建了一个 `childRef`,然后在 `Child` 组件上引用这个 `ref`。在父组件中,我们创建了一个 `handleClick` 方法,该方法通过 `childRef.current.handleClick()` 调用了 `Child` 组件的 `handleClick` 方法。最后,我们在父组件中渲染了一个按钮,当点击这个按钮时,会调用 `handleClick` 方法,从而触发 `Child` 组件的 `handleClick` 方法。
需要注意的是,只有在 `Child` 组件挂载到 DOM 中后,`childRef.current` 才会有值,因此要确保在调用 `handleClick` 方法之前 `Child` 组件已经挂载到了 DOM 中。
// 子组件
import React, { useImperativeHandle, useRef } from 'react';
const ChildComponent = React.forwardRef((props, ref) => {
const childRef = useRef();
// 子组件的方法
const myMethod = () => {
console.log('This is a method from ChildComponent');
};
// 使用useImperativeHandle来暴露方法
useImperativeHandle(ref, () => ({
myMethod: myMethod
}));
// 子组件的渲染逻辑
return <div>I am the ChildComponent</div>;
});
export default ChildComponent;
// 父组件
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const childRef = useRef(null);
// 调用子组件的方法
const callChildMethod = () => {
if (childRef.current) {
childRef.current.myMethod();
}
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={callChildMethod}>Call Child Method</button>
</div>
);
};
export default ParentComponent;
五、全局状态管理Redux
依赖安装
yarn add redux react-redux redux-actions --save -D
创建reducers
// 子reduce.js文件
import { handleActions } from "redux-actions";
const initialState = {
match: {
status: true,
},
count: 1,
};
export default handleActions(
{
UPDATE_MATCH(state, action) {
return Object.assign({}, state, action.payload);
},
UPDATE_COUNT(state, action) {
return Object.assign({}, state, action.payload);
},
},
initialState
);
// 使用index.js文件,聚合所有文件夹的reduce
import { combineReducers } from "redux";
import root from "./root";
const rootReducer = combineReducers({
root,
});
export default rootReducer;
创建actions
import { createAction } from "redux-actions";
export const updateMatch = createAction("UPDATE_MATCH");
export const updateCount = createAction("UPDATE_COUNT");
创建store
// src/store/index.js
import { createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunkMiddleware from 'redux-thunk';
import logger from 'redux-logger'; // 日志
import rootReducer from '../reducers';
const routeMiddleware = routerMiddleware();
let createStoreWithMiddleware;
if (process.env.NODE_ENV !== 'production') {
createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
routeMiddleware,
logger
)(createStore);
} else {
createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
routeMiddleware
)(createStore);
}
const store = createStoreWithMiddleware(rootReducer);
export default store;
穿透整个项目文件
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import { Router, Switch, Redirect, Route } from "react-router-dom";
import history from "./utils/history";
import routes from "./router/config";
import { Provider } from "react-redux";
import store from "@store/configureStore";
const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(
<Provider store={store}>
<Router history={history}>
<Suspense fallback={<div>loading...</div>}>
<Switch>
{routes.map((item, index) => {
return (
<Route key={index} path={item.path} component={item.component} />
);
})}
<Redirect from="/*" to="/" />
</Switch>
</Suspense>
</Router>
</Provider>
);
项目文件使用
import { useSelector, useDispatch } from "react-redux";
import { updateCount } from "@actions/root";
const Index = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state);
const handleClick = () => {
dispatch(updateCount({ count: 2 }));
// console.log(data);
};
return (
<div className="about">
<button onClick={() => handleClick()}>改变数据</button>
</div>
);
};
export default Index;
redux异步操作
异步操作- redux-thunk库
//action必须是扁平化对象
1、安装redux-thunk库,并且在reducer的index文件中解构applyMiddleware
$yarn add redux-thunk
// applyMiddleware表示中间件
import { createStore, applyMiddleware } from "redux";
//引入redux-thunk
import thunk from "redux-thunk";
// applyMiddleware执行的时候要接收一个参数, 里面接收redux的异步库
const store = createStore(reducer, applyMiddleware(thunk));
2、创建一个异步函数去调用同步函数,改变数据
export const loadAsyncAction = () => {
// redux-thunk异步库允许我们在这里return函数, 并且可以接收dispatch接收参数
// 而且这个函数会自动执行
return (dispatch) => {
// 数据请求
fetch(
"https://www.fastmock.site/mock/918b096c85edca5de807f1f8398af51e/api/list"
)
.then((response) => response.json())
.then((res) => {
dispatch(loadAction(res.list));
});
};
};
const loadAction = (payload) => {
return {
type: "load",
payload: payload,
};
};
六、React Router(默认是history模式)
React Router 工作原理的简要概述::
React Router 是一个流行的库,用于在 React 应用程序中处理路由。它允许你定义应用程序的路由结构,并且可以基于当前的 URL 来渲染不同的组件。
1. 路由配置:使用 <Route> 和 <Switch> 组件来定义路由。<Route> 组件用于定义单个路由,而 <Switch> 是一个容器组件,它渲染第一个匹配的 <Route>。
2. 路径匹配:React Router 使用路径模式匹配来确定哪个组件应该被渲染。这包括精确匹配和模糊匹配。
3. 动态路由:使用参数化路由,例如 /users/:id,可以捕获 URL 中的动态部分,并将其作为参数传递给组件。
4. 导航:使用 <Link> 组件来创建导航链接,它是一个可点击的元素,允许用户在应用程序的不同路由之间导航,而不会重新加载页面。
5. 编程式导航:除了使用 <Link> 组件外,React Router 还提供了 useHistory 和 useNavigate 钩子,允许你通过代码来改变当前的路由。
6. 路由守卫:可以在路由之间设置守卫,例如,通过 Prompt 组件来询问用户是否离开当前页面,或者使用 Redirect 组件来重定向用户到另一个路由。
7. 嵌套路由:React Router 支持嵌套路由,允许你定义一个路由的子路由。
8. 懒加载:React Router 支持代码拆分和懒加载,这意味着只有在需要时才会加载路由组件,从而提高应用程序的性能。
9. 历史记录管理:React Router 管理浏览器的历史记录,允许你使用 push 和 replace 方法来添加或替换历史记录条目。
10. 上下文 API:React Router 提供了一个上下文 API,允许你在应用程序中访问路由信息,如当前的路径、路由参数等。
React Router 的核心是它的路由匹配算法和对浏览器历史对象的封装,使得在单页应用程序中导航变得简单和高效。随着 React Router v6 的发布,API 有了较大的变化,提供了更多的灵活性和功能。
hash模式和history模式的区别:
hash模式
这里的 hash 就是指 url 尾巴后的 # 号以及后面的字符。这里的 # 和 css 里的 # 是一个意思。hash 也 称作 锚点,本身是用来做页面定位的,她可以使对应 id 的元素显示在可视区域内。
由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制。
hash 路由:监听 url 中 hash 的变化,然后渲染不同的内容,这种路由不向服务器发送请求,不需要服务端的支持;
当页面中的 hash 发生变化时,会触发hashchange事件。
history模式
首先,hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,hash 的传参是基于 url 的,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。
history 模式改变 url 的方式会导致浏览器向服务器发送请求,这不是我们想看到的,我们需要在服务器端做处理:如果匹配不到任何静态资源,则应该始终返回同一个 html 页面。
history 路由:监听 url 中的路径变化,需要客户端和服务端共同的支持;
○ back():后退到上一个路由;
○ forward():前进到下一个路由,如果有的话;
○ go(number):进入到任意一个路由,正数为前进,负数为后退;
○ pushState(obj, title, url):前进到指定的 URL,不刷新页面;
○ replaceState(obj, title, url):用 url 替换当前的路由,不刷新页面;
总结
1、hash模式较丑,history模式较优雅;
2、pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL;
3、pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中;
4、pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串;
5、pushState可额外设置title属性供后续使用;
6、hash兼容IE8以上,history兼容IE10以上;
7、history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误。
基础路由
1、安装react-router-dom
$yarn add react-router-dom
2、从react-router-dom中解构 BrowserRouter,Link,Route,Switch
// 如果我们的项目要使用路由,要在整个项目的最外面套一个BrowserRouter组件
//实现地址变化使用Link组件
//Route要匹配渲染,path属性和component属性
//react的路由默认是包容性路由,使用exact属性(精准匹配)或者Switch组件是将路由变成排他性路由, 分支匹配
import React, { Component } from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
const Home = () => <div>home</div>;
const About = () => <div>About</div>;
const Topics = () => <div>Topics</div>;
class App extends Component {
state = {};
render() {
return (
<BrowserRouter>
<h1>你好--路由</h1>
<ul>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/about"> about</Link>
</li>
<li>
<Link to="topics">topics</Link>
</li>
</ul>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/topics" component={Topics}></Route>
</Switch>
</BrowserRouter>
);
}
}
export default App;
嵌套路由
//引入模块
import React, { Component } from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
//根路由
class App extends Component {
render() {
return (
<BrowserRouter>
<h1>路由嵌套</h1>
<ul>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/about">about</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/topics" component={Topics} />
</Switch>
</BrowserRouter>
);
}
}
export default App;
//一级路由
const About = () => {
return <div>about</div>;
};
const Topics = () => {
return <div>topics</div>;
};
//嵌套子路由
const HomeFirst = () => <div>home-first</div>;
const HomeSec = () => <div>home-sec</div>;
const HomeThree = () => <div>home-three</div>;
//嵌套路由
const Home = () => {
return (
<div>
<h1>home</h1>
<ul>
<li>
<Link to="/home/home-first">home-first</Link>
</li>
<li>
<Link to="/home/home-sec">home-sec</Link>
</li>
<li>
<Link to="/home/home-three">home-three</Link>
</li>
</ul>
<Switch>
<Route path="/home/home-first" component={HomeFirst} />
<Route path="/home/home-sec" component={HomeSec} />
<Route path="/home/home-three" component={HomeThree} />
</Switch>
</div>
);
};
动态参数
import React, { Component } from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
const Home = () => <div>home</div>;
const Topics = (props) => {
//获取传过来的id: props.match.params.id
return <div> topics-id :{props.match.params.id} </div>;
};
class App extends Component {
state = {};
render() {
return (
<BrowserRouter>
<h1>你好--动态路由</h1>
<ul>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/topics/1">Topics1</Link>
</li>
<li>
<Link to="/topics/2">Topics2</Link>
</li>
</ul>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/topics/:id" component={Topics}></Route>
</Switch>
</BrowserRouter>
);
}
}
路由渲染
//在Route里面要渲染组件,要用component属性
//除了component属性,还可以用render属性来渲染组件;render只能渲染函数组件不能渲染类组件、不能直接获取路由信息;
//改写并获得路由信息:
<Route path="/home" render={(props)=><Home {...props} />}></Route>
//还可以用chilren属性去渲染;children属性也只能渲染函数组件;不管path是否匹配都渲染, 一般会和Switch一起使用
//还可以将组件直接写在Route组件的中间而不使用component属性;这种写法拿不到路由信息
路由重定向
//解构 Redirect 组件
//搭配switch组件使用exact属性精准匹配
<Switch>
<Redirect from="/" to="/home" exact></Redirect>
</Switch>
路由鉴权
//在Route组件中,使用render渲染函数组件,返回结果前进行判断,如果不满足条件则使用重定向(Redirect)另一个页面
<Switch>
<Route path="/userCenter"
render={() => { localStorage.getItem("token") ?<userCenter /> :<Redirect to="/login"> </Redirect> } ></Route> // 鉴权
<Route path="/login" component={Login}></Route>
<Route path="*" component={NotFound}></Route> //404路由,使用的时候配合Switch组件,并且写在最后
</Switch>
Navlink标签
//Navlink也是用来做路由跳转的,可以完全取代 link ,比link多了个css高亮的效果,默认类名为active
//解构Navlink组件
import { NavLink } from "react-router-dom";
//使用的时候需要精准匹配
<li><NavLink to="/home" exact >home</NavLink></li>
//使用activeClassName属性修改默认类名
<li><NavLink to="/home" exact activeClassName = "abc" >home</NavLink></li>
//使用activeStyle属性修改选中时样式
<li><NavLink to="/home" exact activeStyle = {{color : red }} >home</NavLink></li>
路由传参
1、动态参数
// props.match.params.id
2、url传参
<Link to="/topics/2" search:"?a=3&b=4" >About</Link>
//使用H5自带的URLSearchParams中的get()方法去匹配取出参数
const About = (props) => {
const {
location: { search },
} = props;
const a = new URLSearchParams(search);
console.log(a.get("a"));
return <div>about</div>;
};
3、state传参
<Link to="/topics/2" state:{msg:"hello"} >Topics2</Link>
//在该传参的组件中打印 props.location 获取参数
const Topics = (props) => {
console.log(props.location);
return <div>Topics</div>;
};
监听路由变化
如果你使用的是React Router v4或v5,可以通过history对象来监听路由变化。
history.listen(location => {
console.log(location.pathname); // 打印当前路由路径
});
import { useHistory, useLocation } from 'react-router-dom';
function MyComponent() {
let history = useHistory();
let location = useLocation();
useEffect(() => {
console.log('路由变化:', location.pathname);
}, [location]);
// 你可以使用history对象来编程式导航
const handleNavigate = () => {
history.push('/new-path');
};
return (
<button onClick={handleNavigate}>导航到新路径</button>
);
}
懒加载Lazy
import函数
//import 命令
import { add } from './math';
console.log(add(16, 26));
//import函数
import("./math").then(math => {
console.log(math.add(16, 26));
});
lazy函数和Suspense组件-组件中的使用
1、解构lazy、Suspense
import { lazy, Suspense } from "react";
2、使用lazy配合import引入组件
const Child = lazy(() => import("./Child"));
3、使用Supense包裹元素,切Supense中还有个属性 fallback ,里面写一个加载过程中显示内容的组件
<Suspense fallback={<div>loading...</div>}>
{this.state.isShow && <Child />}
</Suspense>
lazy函数和Suspense组件-路由中的使用
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));
class App extends Component {
render() {
return (
<BrowserRouter>
<ul>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/about">about</Link>
</li>
</ul>
<Suspense fallback={<div>loading...</div>}>
<Switch>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Switch>
</Suspense>
</BrowserRouter>
);
}
}
七、装饰器
withRoute装饰器
1、下载安装依赖
yarn add @babel/core @babel/plugin-proposal-decorators @babel/preset-env
2、创建 .babelrc
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
3、创建config-overrides.js
const path = require('path')
const { override, addDecoratorsLegacy } = require('customize-cra')
function resolve(dir) {
return path.join(__dirname, dir)
}
const customize = () => (config, env) => {
config.resolve.alias['@'] = resolve('src')
if (env === 'production') {
config.externals = {
'react': 'React',
'react-dom': 'ReactDOM'
}
}
return config
};
module.exports = override(addDecoratorsLegacy(), customize())
4、安装依赖
yarn add customize-cra react-app-rewired
5、修改package.json
...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
...
6、使用withRoute
import { withRouter } from "react-router-dom";
//在需要获得路由信息的组件前使用
@withRoute
移动端适配
移动端适配(先添加装饰器配置)
1、在config-overrides.js中的customize-cra去配置,customize-cra是一个NPM包,用来做webpack的配置;
2、下载插件 postcss-px2rem
3、修改config-overrides.js文件(新增以下内容)
const {addPostcssPlugins } = require("customize-cra");
module.exports = override(
addPostcssPlugins([require("postcss-px2rem")({ remUnit: 37.5 })])
);
八、HOC高阶组件
// 高阶组件=>函数, 传入一个组件(函数组件、类组件),返回一个新组件
// 作用: 增强组件的功能,复用
//封装一个高阶组件js文件
const withHoc = (Comp) => {
return class extends Component {
render() {
console.log(this.props);
return (
<>
<Comp msg="hello" {...this.props} /> //展开合并原有属性
</>
);
}
};
}
//在其他组件中使用
export default withHoc(App);