React基础
JSX
- 全称: JavaScript XML
- react定义的一种类似于XML的JS扩展语法: JS + XML本质是
React.createElement(component,props,...children)方法的语法糖 - 作用: 用来简化创建虚拟DOM
- 写法:
var ele = <h1>Hello JSX!</h1> - 注意1:它不是字符串, 也不是HTML/XML标签
- 注意2:它最终产生的就是一个JS对象
- 写法:
- 标签名任意: HTML标签或其它标签
- 标签属性任意: HTML标签属性或其它
- 基本语法规则
- 遇到
<开头的代码, 以标签语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析 - 遇到以
{开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
- 遇到
- babel.js的作用
- 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
- 只要用了JSX,都要加上
type="text/babel", 声明需要babel来处理
函数组件和class组件
注意
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
三大属性:props,state,refs
props
类式组件
<script type="text/babel">
//创建组件
class Person extends React.Component {
//构造器是否接收props,是否传递super,取决于:是否希望在构造器中通过this访问props
constructor(props){
//如果不写,this.props为undefined
super(props);
}
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
static defaultProps = {
sex: "未知",
age: "18",
}
render() {
// this Person实例对象
console.log(this);
//props是只读属性,不可以修改
// this.props.name = "34";错误
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="tom" age={12} sex="女" speak={console.log(123)} />, document.getElementById('test0'))
</script>
函数式组件
<script type="text/babel">
// 函数式组件只能使用props
function Person(props) {
const { name, sex, age } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
//对prop的类型进行限制,类型,必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,//限制为函数
}
//指定标签的默认属性
Person.defaultProps = {
sex: "未知",
age: "18",
}
ReactDOM.render(<Person name="haha" age={12} sex="女" />, document.getElementById("test"));
</script>
props-types 和defaultProps
函数式组件
<script type="text/babel">
function Person(props) {
const { name, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
</ul>
);
}
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
Person.defaultProps = {
sex: "女",
age: 18,
};
ReactDOM.render(
<Person name="tom" sex="男" />,
document.getElementById("test")
);
</script>
类式组件
class Person extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
// 设置默认属性
static defaultProps = {
sex: "女",
age: 18,
};
state = {};
render() {
const { name, sex, age } = this.props;
// prop是只读的
// this.props.name = "jack";
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
}
state
注意
- 组件中render方法中的this为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this: 通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
函数式组件————useState
import { useState } from "react";
import "./App.css";
export default function App() {
//初始化state
const [todos, setTodos] = useState(() => {
return [
{ id: "001", name: "吃饭", done: false },
{ id: "002", name: "睡觉", done: true },
{ id: "003", name: "打豆豆", done: false },
];
});
function addTodo(todoObj) {
const newTodo = [todoObj, ...todos];
//更新state
setTodos(newTodo);
}
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={addTodo} />
<List todos={todos} />
<Footer />
</div>
</div>
);
}
类式组件
import React, { Component } from "react";
export default class App extends Component {
state={
todos:[
{ id: "001", name: "吃饭", done: false },
{ id: "002", name: "睡觉", done: true },
{ id: "003", name: "打豆豆", done: false },
]
}
addTodo = (todoObj) => {
const newTodo = [todoObj, ...this.state.todos];
this.setState({todos: newTodo})
};
render() {
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<List todos={this.state.todos} />
<Footer />
</div>
</div>
);
}
}
refs
使用形式
- 字符串形式的ref:
<input ref="input1"/> - 回调形式的ref:
<input ref={(c)=>{this.input1 = c}}/> - createRef创建ref容器
myRef = React.createRef();<input ref={this.MyRef}/>
事件处理
注意
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件——为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)————为了的高效
- 通过event.target得到发生事件的DOM元素对象—--—不要过度使用ref
<script type="text/babel">
class Demo extends React.Component {
myRef = React.createRef();
myRef1 = React.createRef();
showDataL = () => {
alert(this.myRef.current.value)
}
showDataR = () => {
alert(this.myRef1.current.value)
}
render() {
//html模板
//c表示当前节点
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showDataL}>点击提示左侧数据</button>
<input ref={this.myRef1} onBlur={this.showDataR} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById("test"))
</script>
生命周期
旧
新
虚拟DOM和DOM diffing算法
React 脚手架 Ajax
介绍
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置(语法检查、jsx编译、devServer…)
- 下载好了所有相关的依赖
- 可以直接运行一个简单效果
- react提供了一个用于创建react项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
创建项目
- 全局安装
npm i -g create-react-app - 切换到想创项目的目录,使用命令
create-react-app xxx - 进去项目文件夹
cd xxx - 启动项目
npm start
脚手架目录分析
public ---- 静态资源文件夹
favicon.icon------ 网站页签图标index.html--------主页面manifest.json----- 应用加壳的配置文件robots.txt-------- 爬虫协议文件
src ---- 源码文件夹
App.css-------- App组件的样式App.js--------- App组件App.test.js---- 用于给App做测试index.css------ 样式index.js-------入口文件logo.svg------- logo图reportWebVitals.js-------页面性能分析文件(需要web-vitals库的支持)setupTests.js------ 组件单元测试的文件(需要jest-dom库的支持)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- %PUBLIC_URL%表示public下的路径 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- 开启理想视口,为了适配移动端 -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<!-- 用于指定网页添加到手机到手机主屏幕后的图标 -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- 应用加壳时的配置文件,就是网页版转手机版的配置 -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<!-- 浏览器不支持js运行时显示 -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
app.jsx
// 函数式组件
export default function App() {
return (
<h1>HEllO</h1>
);
}
//类式组件
// 属于分别暴露的引用,不是解构赋值
import {Component} from "react"
export default class App extends Component{
render(){
return(
<h1>HEllO</h1>
)
}
}
index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// concurrent 模式:终极模式
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
// legacy 模式:这个模式是当前React App使用的模式,但是可能不支持某些新特性
ReactDOM.render(<App/>,document.getElementById('root'))
TodoList案例
- nanoid()随机生成id :
npm i nanoid - 更新一个对象
<script>
let obj = {a:1,b:2};
let obj1 = {...obj,b:3};
console.log(obj1);//a:1,b:3
</script>
- defaultChecked :只在页面的第一次渲染时有用
脚手架配置代理
方法一
在package.json中追加如下配置:
"proxy":"http://localhost:5000"
缺点:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
- 第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
- 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
存在问题
由于版本问题会出现无法访问的情况,上述配置是使用低版本时的配置
高版本需要改变配置为
const {createProxyMiddleware} = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
createProxyMiddleware("/api1", {
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: { "^/api1": "" }, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
})
);
};
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
搜索案例
知识点
- 连续解构赋值
const data = {a:{b:{c:1}}}
const data = {a:{b:1}}
const {a:{b:{c}}} = data;//联系解构赋值
console.log(c);//1
const {a:{b:sum}} = data;//连续解构赋值并对b进行重命名
console.log(sum);//1
消息的订阅与发布——PubSub.js
fetch发送请求
React路由
SPA应用
- 单页面应用(SPA)
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新页面,只会做页面的局部刷新
- 数据都需要通过ajax请求获取,并在前端异步显示
路由理解
后端路由 path-function 前端路由 path-component
前端路由的实现原理
history 锚点跳转:
<a href="#demo1"></a>
<a href="#demo2"></a>
<a href="#demo3"></a>
路由的基本使用(react-router-dom5版本)
路由组件与一般组件的区别
- 写法不同:
一般组件:
<Demo/>路由组件:<Route path="/about" component={About}/> - 存放位置不同:
一般路由:
components路由组件:pages - 接收的props不同 一般组件: 传什么收什么 路由组件:接收三个固定参数
history:
go:
goBack:
goForward:
push:
replace:
location:
pathname:
search:
state:
match:
params:
path:
url:
NavLink
activeClassName用于设置被点击时的class如果存在active的类名就不需要写activeClassName NavLink相比于Link有高亮效果
<NavLink
activeClassName="bgc"
className="list-group-item"
to="/about"
>
About
</NavLink>
二次封装NavLink
props可以接受到标签体中的内容,在this.props.children属性中
MyNavLink组件
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="bgc" className="list-group-item" {...this.props}/>
);
}
}
MyNavLink使用
<MyNavLink to='/about'>About</MyNavLink>
Switch组件
注册路由时使用Switch包裹,匹配成功之后不往下匹配
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
</Switch>
多层样式丢失问题
目录结构
出错时的样式引入,index.html中
<link rel="stylesheet" href="./css/bootstrap.css">
出错时的路由链接
<div className="list-group">
<MyNavLink to="/xxx/about">About</MyNavLink>
<MyNavLink to="/xxx/Home">Home</MyNavLink>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
<Switch>
<Route path="/xxx/about" component={About} />
<Route path="/xxx/home" component={Home} />
</Switch>
</div>
</div>
</div>
解决办法
- 改变引用路径
<link rel="stylesheet" href="/css/bootstrap.css"> - 改变引用路径
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">(只适用react脚手架) - 使用HashRouter包裹不使用BrowserRouter(一般不使用这种)
路由的模糊匹配和严格匹配
默认模糊匹配 Route匹配MyNavLink组件,一层一层一层匹配,最终效果不变
精准匹配,给Route加exact属性,有时候会导致二级路由不可使用
<Route exact={true} path="/xxx/about" component={About} />
//或
<Route exact path="/xxx/about" component={About} />
Redirect组件
Redirect写在所有Route组件的最下方
<Switch>
<Route path="/xxx/about" component={About} />
<Route path="/xxx/home" component={Home} />
<Redirect to='/home'/>
</Switch>
二级路由(多级路由)
基本使用
- 注册子路由时要写上父路由的path值
- 路由匹配是按照注册路由的顺序进行的
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news" className="list-group-item ">
News
</MyNavLink>
</li>
<li>
<MyNavLink to="/home/messages" className="list-group-item ">
Message
</MyNavLink>
</li>
</ul>
<ul>
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/messages" component={Message}/>
<Redirect path="/home/news"/>
</Switch>
</ul>
向路由组件传递params参数
传递params
<div>
<ul>
{this.state.messages.map((message) => {
return (
<li key={message.id}>
<Link
to={`/home/messages/detail/${message.id}/${message.title}`}
>
{message.title}
</Link>
</li>
);
})}
</ul>
<Route path="/home/messages/detail/:id/:title" component={Detail} />
</div>
接收params
const {id,title} = this.props.match.params
向路由组件传递search参数
- 路由链接(携带参数):
<Link to={`/home/messages/detail?name=tom&age=18`}>xxx</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/home/messages/detail" component={Detail} /> - 接受参数:
const {search} = this.props.location
注意
获取到的search是urlencoded编码字符串,需要借助querystring解析
querystring是react脚手架自带的一个库
urlencoded编码 : key=value&key = value
import qs from 'querystring
let obj = {name:'tom',age:18}
console.log(qs.stringify(obj));//name=tom&age=18
let str = name=tom&age=18;
console.log(qs.parse(str));//{name:'tom',age:18}
向路由组件传递state参数
- 路由链接(携带参数):
<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>xxx</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Detail} /> - 接受参数:
const {state} = this.props.location
注意
刷新也可以保留参数,因为使用的是BrowserRouter,但是如果清除浏览器缓存则state将变成undefined
push和replace
默认push replace的设置
<Link replace={true} to='path:'/demo/test'>xxx</Link>
//或
<Link replace to='path:'/demo/test'>xxx</Link>
编程式路由(this.props.history上的属性)
借助this.props.history对象上的API对操作路由跳转、前进、后退
- this.props.history.
push('xxx'):xxx在传递params和search参数时直接写/组件中to的的值,在传递state参数时,xxx第一个参数写路径,第二个参数携带的state参数this.props.history.push('/home/message/detail',{id,title}) - this.props.history.
replace('xxx'):xxx在传递params和search参数时直接写/组件中to的的值,在传递state参数时,xxx第一个参数写路径,第二个参数携带的state参数this.props.history.replace('/home/message/detail',{id,title}) - this.props.history.
goBack() - this.props.history.
goForward() - this.props.history.
go(xxx): xxx写数值,正数前进,负数回退
withRouter组件
一个函数,加工一般组件,让一般组件拥有路由组件的API
BrowserRouter和HashRouter的区别
- 底层原理不一样:
BrowserRouter使用的是h5的historyAPI,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值 - path表现形式不一样
BrowserRouter的路径中没有#
HashRouter的路径中包含# - 刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中
HashRouter刷新后会导致路由state1参数的丢失 - 备注:HashRouter可以解决一些路径错误相关的问题
redux
redux是什么
- redux是一个专门用于做状态管理的js库(不是react插件库)
- 它可以用在react,angular,vue等项目中,但基本与react配合使用
- 作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用
redux工作原理
Action
- 动作的对象
- 包含2个属性
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
- 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的state和action, 产生新的state的纯函数。
store
- 将state、action、reducer联系在一起的对象
- 如何得到此对象?
- import {createStore} from 'redux'
- import reducer from './reducers'
- const store = createStore(reducer)
- 此对象的功能?
- getState(): 得到state
- dispatch(action): 分发action, 触发reducer调用, 产生新的state
- subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
求和案例
异步编程问题
- redux默认是不能进行异步处理的,
- 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
使用异步中间件:npm install --save redux-thunk
监听状态变化
- 在子组件中写
componentDidMount(){
//检测redux中状态的变化,只要变化就调用render()
store.subscribe(()=>{
//假装更新,实则调用一个render()
this.setState({})
})
}
- 在
index.js中写
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import store from "./redux/store";
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
store.subscribe(() => {
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
});
核心API
- createstore() 作用:创建包含指定reducer的store对象
- store对象
- 作用: redux库最核心的管理对象
- 它内部维护着:state,reducer
- 核心方法:
- getState()
- dispatch(action)
- subscribe(listener)
- 具体编码:
- store.getState()
- store.dispatch({type:'INCREMENT', number})
- store.subscribe(render)
- applyMiddleware() 作用:应用上基于redux的中间件(插件库)
- combineReducers() 作用:合并多个reducer函数
react-redux
两类组件
- UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
- 容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
相关API
- Provider:让所有组件都可以得到state数据
<Provider store={store}>
<App />
</Provider>
- connect:用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
connect(
mapStateToprops,
mapDispatchToProps
)(Counter)
- mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
return {
value: state
}
}
- mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
一个容器组件
//引入UI组件
import CountUI from '../../components/Count'
//引入连接UI组件的redux
import { connect } from 'react-redux'
import {createIncrementAction} from '../../redux/count_action'
/*
1.mapStateToProps函数返回的是一个对象
2.返回的对象中的key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象
2.返回的对象中的key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递方法
*/
function mapDispatchToProps(dispatch){
return {
jia: number => dispatch(createIncrementAction(number)),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
简化版本
//引入UI组件
import CountUI from "../../components/Count";
//引入连接UI组件的redux
import { connect } from "react-redux";
import { createIncrementAction } from "../../redux/count_action";
export default connect((state) => ({ count: state }), {
jia: createIncrementAction,
})(CountUI);
优化版本
//引入连接UI组件的redux
import { connect } from "react-redux";
import { createIncrementAction } from "../../redux/count_action";
import React, { Component } from 'react'
class CountUI extends Component {
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1);
}
decrement = ()=>{
const { value } = this.selectNumber;
}
incrementAsync = ()=>{
const {value} = this.selectNumber
}
render() {
return (
<div>
<h1>当前数据为 {this.props.count}</h1>
<select ref={(c) => (this.selectNumber = c)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button>奇数时+</button>
<button onClick={this.incrementAsync}>异步+</button>
</div>
);
}
}
export default connect((state) => ({ count: state }), {
jia: createIncrementAction,
})(CountUI);
纯函数和高阶函数
纯函数
- 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
- redux的reducer函数必须是一个纯函数
高阶函数
扩展内容
setState
setState(stateChange, [callback])------对象式的setState- stateChange为状态改变对象(该对象可以体现出状态的更改)
- callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
setState(updater, [callback])------函数式的setState- updater为返回stateChange对象的函数。
- updater可以接收到state和props。
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
- 总结:
- 对象式的setState是函数式的setState的简写方式(语法糖)
- 使用原则:
- 如果新状态不依赖于原状态 ===> 使用对象方式
- 如果新状态依赖于原状态 ===> 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
2. lazyLoad
路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
//3.<Suspense fallback={<Loading/>}>,Loading组件不能使用懒加载
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3. Hooks
1. React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
3. State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5. Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
4. Fragment
使用
<Fragment><Fragment>
<></>
作用
可以不用必须有一个真实的DOM根标签了
5. Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
3) 后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer>
{
value => ( // value就是context中的value数据
要显示的内容
)
}
</xxxContext.Consumer>
注意
在应用开发中一般不用context, 一般都用它的封装react插件
6. 组件优化
Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
Router6
Components
1.<BrowserRouter/>
- 作用:用于包裹整个应用
- 示例:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
{/*整体结构(通常为App组件)*/}
</BrowserRouter>
)
2.<HashRouter/>
- 说明:作用与
<BrowserRouter/>一样,但<HashRouter/>修改的是地址栏的hash值 - 备注:6.x版本中
<BrowserRouter/>和<HashRouter/>的用法与5.x相同
3.<Routes/>与<Route/>
- v6版本中移出了先前的
<Switch/>,引入了新的代替者:<Routes/> <Routes/>与<Route/>要配合使用,且必须要用<Routes/>包裹<Route/><Route/>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件<Route caseSensitive/>属性用于指定:匹配时是否区分大小写(默认为false)- 当URL发生变化时,
<Routes/>都会查看其所有的Route元素以找到最佳匹配并呈现组件 <Route/>也可以嵌套使用,且可以配合useRoutes()配置“路由表”,但需要通过<Outlet/>组件来渲染其子路由- 示例代码
<Routes>
{/* path属性用于定义路径,element属性用于定义当前路径所对应的组件 */}
<Route path="/login" element={<Login/>} />
{/* 用于定义嵌套路由,home是一级路由,对应的路径/home */}
<Route>
{/* test1和test2是二级路由,对应的路径是/home/test1或/home/test2 */}
<Route path="test1" element={<Test1/>} />
<Route path="test2" element={<Test2/>} />
</Route>
{/* Route也可以不写element属性,这时就是用于展示嵌套路由,所对应的路径/users/xxx */}
<Route path='users'>
<Route path='xxx' element={<Demo/>}/>
</Route>
</Routes>
5.<NavLink/>
- 作用:与
<Link/>组件类似,且可以实现导航高亮的效果 - 示例:
// 注意:NavLink默认类名是active,下面是指定自定义的class
//自定义样式
<NavLink to="login" className={({isActive})=>{
console.log('home',isActive);
return isActive ? 'base one' : 'base'
}}>
login
</NavLink>
/*
默认情况下,当Home的子组件匹配成功,Home的导航也会高亮
当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果
*/
<NavLink to='home' end>home</NavLink>
6.<Navigate/>
- 作用:只要
<Navigate/>组件被渲染,就会修改路径,切换视图 - replace属性用于控制跳转模式(push或replace,默认push)
- 示例:
import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'
export default function App() {
const [sum,setSum] = useState(1);
return (
<div>
<h3>HOME</h3>
{/* 根据sum的值决定是否切换视图 */}
{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to='/about' replace/>}
</div>
)
}
7.<Outlet/>
- 当
<Route/>产生嵌套时,渲染其对应的后续子路由
Hooks
useRoutes():根据路由表,动态创建<Routes/>和<Route/>useNavigate():返回一个函数用来实现编程式导航useParams():回当前匹配路由的params参数,类似于5.x中的match.paramsuseSearchParams():用于读取和修改当前位置的URL中的查询字符串,返回一个包含两个值的数组,内容分别为:当前的search参数,更新search的函数useLocation():获取当前location信息,对标5.x中的路由组件的location属性useMatch():返回当前匹配信息,对标5.x中路由租价的match属性
问题
ReactDOM.render()和React.createRoot().render()
// concurrent 模式:终极模式
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
// legacy 模式:这个模式是当前React App使用的模式,但是可能不支持某些新特性
ReactDOM.render(<App/>,document.getElementById('root'))
Error:React limits the number of renders to prevent an infinite loop.
原因:recat限制渲染次数,以防止无限渲染和渲染次数过多
function handleMouseLeave(flag) {
setMouse(flag);
};
解决:
function handleMouseLeave(flag) {
return () => setMouse(flag);
};
Error: React Hook "useState" is called in function "index" that is neither a React function component nor a custom React Hook function
原因:在不是react的函数中调用ReactHook"useState"
解决:
A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
react-router-dom的版本问题
6版本需要包<Routes>