react是一个将数据渲染为html视图的开源javascript库
主要负责的是操作dom呈现界面,关于获取数据和处理数据还需要自己去弄
react由国外知名公司Facebook开发的,且开源
原生js操作dom繁琐且效率低
使用js直接操作dom,浏览器会进行大量的重绘重排
原生js没有组件编码方案,代码复用率低
react使用的jsx语法,并非js语法
采用组件化模式,和vue一样,声明式编码,提高开发效率及组件复用率
在react native中可以使用react语法进行移动端开发
使用的虚拟dom和diffing算法,减少与真实dom的交互
本质上虚拟dom就是一个object对象(一般对象)
虚拟dom比较”轻“,真实dom比较”重“,因为虚拟dom是react内部在用,没有那么多的属性
虚拟dom最终会转换成真实dom,呈现在界面上
全称:JavaScript XML
react定义的一种类似于XML的JS扩展语法
定义虚拟dom的时候不要用引号
标签中混入js表达式时要用{}
样式的类名不要使用class,要使用className
内联样式要用style={{key: value}}的形式去写
虚拟dom必须只有一个根标签
标签必须闭合
若标签首字母为小写,则将标签转换成html中的同名标签,若无该标签,则报错
若标签首字母为大写,react会去渲染对应的组件,若组件没有定义,则报错
{}里面才可以写js表达式,js里面注释/**/
return (
{/*p1
*/}
p2
)
语句例如:if(){},for(){},switch(){}
表达式:a,a+b,demo(1),arr.map(),function test (){}
先进行声明函数
里面通过return返回标签结构
函数名必须大写,否则react会去寻找html标签
然后通过标签形式进行使用该组件即可
将该组件填充刚到id为box的容器中
function MyDemo(){
return 我是函数式组件
}
ReacrDOM.render( ,document.getElementById("box"))
react先解析组件标签,找到MyDemo组件
发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转换成真实得到DOM,然后呈现在界面上
先进行声明类式组件
里面必须有一个render方法返回标签结构
类名大写且必须继承React.Component
然后通过标签形式进行使用即可
将该组件填充刚到id为box的容器中
class MyComponent extends React.Component {
render(){
return 我是类式组件
}
}
ReacrDOM.render( ,document.getElementById("box"))
react先解析组件标签,找到MyComponent组件
发现组件是类定义的,随后new出该类的实例,并通过该实例找到render方法,将render返回的虚拟DOM转换成真实得到DOM,然后呈现在界面上
有state的组件就叫做复杂组件,没有的叫做简单组件
state就是状态,简单来说就是存放数据的地方,它可以驱动界面
setState用来修改state中的数据
setState有两种写法
第一种是对象写法,也叫做状态改变对象,也是一种简写
第二种就是函数写法,是一个函数方式,通过返回修改,里面可以拿到当前的state和传递过来的props
两种都有一个第二个参数,是一个回调函数,因为setState是异步的,如果想直接查看修改的结果就需要在回调函数中查看
this.setState((state,props) => {
return {count: state.count + 1}
})
使用情况
如果新状态不依赖于原状态,就使用简写,如果依赖于,就使用函数,例如在原来数字的基础上加一
传统的绑定事件,只不过所有on之后的第一个字母大写
事件不需要带小括号,否则会立刻执行,拿到返回的结果交给这个事件
在类式组件里面,所创建的方法都是使用赋值来进行定义方法,也需要使用到箭头函数,因为在里面定义的方法是没有this指向的
在函数式组件里面就是定义函数方法
组件的自定义方法this为undefined
如何解决自定义方法的this指向:强行绑定通过bind(),箭头函数
class MyComponent extends React.Component {
//类式组件的定义方法
demo = () => {
console.log("触发了事件")
}
render(){
return 我是类式组件
}
}
state是组件对象最重要的属性,值是对象(可以包含多组key: value的组合)
组件被称为状态机,通过更新组件state的状态来更新页面显示的效果
组件中render方法的this为组件的实例对象
状态数据不可以直接修改,需要调用setState()
修改的数据不是替换,而是合并
class MyComponent extends React.Component {
state = {name:"张三",age:19}
demo = () => {
this.setState({age:30})
}
render(){
return (
姓名:{this.state.name}
年龄:{this.state.age}
)
}
}
每个组件对象都会有props属性
组件标签的所有属性都保存在props中
主要作用是通过标签属性从组件外向组件内传递变化的数据
同时组件内部也不可以进行修改props数据
class MyComponent extends React.Component {
render(){
//从当前实例身上的props解构出来name和age
const {name,age} = this.props
return (
姓名:{this.state.name}
年龄:{this.state.age}
)
}
}
//传递参数的限制,例如名字必须是字符串类型,还必须传入
//写在类里面的话就不用写MyComponent了,但是前面需要加上一个static
//需要下载一个库叫做prop-types
MyComponent.propTypes = {
name:PropTypes.string.isRequired
}
//传递参数的默认,例如年龄,没有传递的时候默认为18
//写在类里面的话就不用写MyComponent了,但是前面需要加上一个static
MyComponent.defaultProps = {
age:18
}
//第一种用法
//第二种用法
const info = {name:"李四",age:30}
用于获取DOM节点
不要过度的使用ref’属性
class MyComponent extends React.Component {
demo = () => {
console.log(this.refs.input1.value)
}
render(){
return (
)
}
}
class MyComponent extends React.Component {
demo = () => {
console.log(this.input1.value)
}
render(){
return (
this.input2 = c}/>
)
}
}
class MyComponent extends React.Component {
myRef01 = React.createRef()
myRef02 = React.createRef()
demo = () => {
console.log(this.myRef01.value)
}
demo2 = () => {
console.log(this.myRef02.value)
}
render(){
return (
)
}
}
react使用的是自定义事件,而不是使用原生的DOM事件,主要是为了更好的兼容
react是通过事件委托的方式处理的,委托给最外层的元素,为了更高效
通过event.target就可以得到当前发生事件的DOM元素对象
简单来说就是输入项,在点击登录的时候通过获取dom节点,然后拿到对应的值
class Login extends React.Component {
login = () => {
const {username,password} = this
alert(`用户名为:$(username.value),密码为:$(password.value)`)
}
render(){
return (
用户名: this.username = c}/>
密码: this.password = c}/>
)
}
}
简单来说就是输入项在改变的时候自动将值存储到state状态中,然后需要的时候直接去state中取就可以了,建议使用这个,因为不需要操作ref,react的原则就是尽量少使用ref
class Login extends React.Component {
state = {
username:''
password:''
}
handerUsername = (event) => {
this.setState({
username:event.target.value
})
}
handerPassword = (event) => {
this.setState({
password:event.target.value
})
}
login = () => {
const {username,password} = this.state
alert(`用户名为:$(username),密码为:$(password)`)
}
render(){
return (
用户名:
密码:
)
}
}
初始化阶段,由ReactDOM.render()触发,初次渲染
constructor() //构造器
componentWillMount() //将要挂载之前
render() //render函数
componentDidMount() //挂载完毕,常用,一般都是在这个钩子里面左一些初始的事
更新状态,由组件内部调用this.setState()或者父组件render触发
componentWillReceiveProps() //用在父子组件,刚开始不会调用,更新的时候会进行调用,里面可以拿到参数props
shouldComponentUpdate() //控制更新的阀门
componentWillUpdate() //将要更新的之前
render() //必须使用的一个
componentDidUpdate() //更新完毕
卸载组件,由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() //常用,一般在这个钩子里面做一些收尾的事
初始化阶段,由ReactDOM.render()触发,初次渲染
constructor() //构造器
getDerivedStateFromProps //需要返回结果,可以返回null或者数据
render() //render函数
componentDidMount() //挂载完毕,常用,一般都是在这个钩子里面左一些初始的事
更新状态,由组件内部调用this.setState()或者父组件render触发
getDerivedStateFromProps //需要返回结果,可以返回null或者数据
shouldComponentUpdate() //控制更新的阀门
render() //必须使用的一个
getSnapshotBeforeUpdate //即将更新之前,可以拿到更新之前的props和state还有getDerivedStateFromProps所返回的数据
componentDidUpdate() //更新完毕
卸载组件,由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() //常用,一般在这个钩子里面做一些收尾的事
render //初始化渲染或更新渲染调用
componentDidMount //开启监听,发送ajax请求
componentWillUnmount //做一些收尾工作,例如清除定时器
在18版本有可能会废弃,现在使用也可以使用,但是会出现警告,18之后使用的话需要加上前缀名UNSAFE_才可以进行使用,不建议使用下面这中方式
componentWillMount
componentWillReceiveProps
componentWillUpdate
react在渲染到界面的时候会先生成虚拟DOM,然后再渲染到界面,当你更新数据的时候,会从新生成虚拟DOM,新的虚拟DOM会和旧的虚拟DOM进行比较,一样的话就直接使用真实DOM,不一样的话替换界面中对应的DOM节点
在真正的开发中,例如循环渲染数据,必须有一个key,那么这个key进行使用id或者唯一标识,不建议使用index作为key,一旦发生破坏顺序的操作,那么就会产生性能问题,例如2000条数据,使用index作为key的话,此时往之前添加一条数据,因为key使用的是index,就会从新将2001条数据全部从新渲染,其实只需要添加一条数据即可,这就是使用index作为key的后果,而使用id或者唯一标识的时候就没有这个问题,index建议使用在不破坏数据顺序的时候,一般有id或者唯一标识的时候尽量使用id或者唯一标识
让函数组件也可以有state状态,并进行对状态的读写操作
//第一次初始化的时候会做缓存,后续不会因为传递的值把之前的给覆盖
//里面有两个元素,xxx是内部当前值,setxxx是更新状态的函数,initvalue是初始值
const [xxx,setxxx] = React.useState(initvalue)
//更新状态的两种方法
setxxx(newvalue)
setxxx(value => newvalue)
让函数组件也可以用于生命周期钩子,并且可以在组件挂载后,状态更新后,组件卸载前做一些操作
如果不加[],且没有返回函数的情况下,这个函数在组件挂载后,状态更新后都会执行
如果加[],且没有返回函数的情况下,这个函数在组件挂载后执行,数组里面可以放置监听的状态,当状态改变的时候就会再次触发,相当于状态更新后的钩子
如果加[],且有返回函数的情况下,这个函数的return就会在卸载之前进行执行
React.useEffect(() => {
return () => {
}
},[])
用于获取元素节点
对应一个只能存储一个
const myRef = React.useRef()
不会渲染到界面,用于不使用div当作跟标签的情况下
也可以使用<>>
两者的区别就是Fragment可以写key,空标签不可以写key’
import {Fragment} from "react"
export default class Demo01 extends Component {
render() {
return (
文字描述一
文字描述二
)
}
}
一种组件之间的通信方式,常用于祖组件和后代组件间的通信
创建Context容器对象,名称首字母大写
祖组件
const XxxContext = React.createContext()
子组件
后代组件,这种写法只限于类式组件
//声明接受context
static contextType = XxxContext
//读取context中的value数据
this.context
第二种接受写法,可以应用于类式组件和函式组件
{
value => (要显示的内容)
}
只要执行this.setState({}),即使不更新状态,也会从新调用render,从而引发效率低
父组件重新render的时候,就会自动重新render子组件,纵使子组件没有用到父组件的任何数据,从而引发效率低
方法一
自身解决方法
在shouldComponentUpdate生命周期钩子里面进行判断,如果当前的状态和即将要改变的状态一致,则返回false,不一致的情况下再返回true
shouldComponentUpdate(nextProps,nextState){
return !this.state.name === nextState.name
}
子组件解决方法
在shouldComponentUpdate生命周期钩子里面进行判断,如果当前接受到父组件的状态和即将要改变时接受到父组件的状态一致,则返回false,不一致的情况下再返回true
shouldComponentUpdate(nextProps,nextState){
return !this.props.name === nextProps.name
}
方法二
使用组件的时候传递数据,类似于vue的插槽技术
不可以传递参数,传递参数使用renderProps
//传递Hello
Hello
//拿取Hello
this.props.children
类似于vue中的插槽技术
用于在组件里面进行占位,然后后续通过程序员放置什么就渲染什么
还可以传递参数,简单来理解就是给A组件传递一个render,是一个函数,函数可以接受一个值name,这个值是A组件传递过来的,然后函数的返回结果就是B组件,同时B组件接受到一个name值,我们只需要在A组件里面指定位置调用一下该函数,就可以渲染内容
}/>
export default class Demo03 extends Component {
state = {
name: "迈巴赫"
}
render() {
return (
名字是:{this.state.carName}
{this.props.render(this.state.name)}
)
}
}
错误边界
例如现在有一个网页,里面有很多子组件,此时因为后端或者其他原因,其中一个组件出错了,则整个页面都报错运行不起来了,所谓的错误边界就是把错误控制在一定范围,不影响到其他地方的展示
错误边界的处理都是在父组件里面进行处理
只能捕获后代组件生命周期产生的错误,无法捕获自身组件产生的错误和其他组件在合成事件,定时器产生的错误
使用方式
//先在父组件的状态里面生命一个是否发生错误
state = {
hasError: false
}
//生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error){
console.log(error)
return {
hasError: true
}
}
//捕获错误信息
componentDidCatch(error,info){
//统计错误信息,然后发送给后台去
console.log(error,info)
}
//进行渲染时候判断
render() {
return (
{this.state.hasError ? '当前网络不稳定,稍请后再试!' : 'hello world'}
)
}
用于帮助快速搭建一个基于React的模板项目,包含了所需要的配置(语法检查,jsx解析,devServer),同时也下载好了所需要的相关依赖,可以直接运行一个简单的效果
react创建项目的脚手架库:create-react-app
项目的整体技术架构为:react+webpack+es6+eslint
使用脚手架开发的项目特点:模块化,组件化,工程化
npm install -g create-react-app
create-react-app 项目名称 创建项目
npm start 启动项目
npm run build 开发完毕进行打包
npm run eject 查看webpack文件,执行之后就不可以进行撤回了
├── .gitignore Git提交忽略文件配置
├── package-lock.json 文件说明以及版本信息
├── package.json 文件说明以及版本信息
├── README.md 项目使用
├── node_modules 所依赖的所有包
├── src 主要目录
│ ├── App.css App组件的样式
│ ├── App.js App组件
│ ├── App.test.js App测试文件
│ ├── index.css 全局公用样式,会在index.js中进行引入
│ ├── index.js React主要文件,入口文件
│ ├── logo.svg React初始图片
│ ├── reportWebVitals.js 用于记录页面性能
│ └── setupTests.js 用于组件,应用测试的
├── public 界面文件夹
│ ├── index.html 主要界面
│ ├── logo192.png 网页图标(iOS浏览器)
│ ├── logo512.png 加壳网页图标
│ ├── manifest.json 应用加壳配置文件
│ ├── robots.txt 爬虫规则
│ └── favicon.ico 网站的图标
创建components文件夹放置所有的组件,里面有对应组件的文件夹
├── src
│ ├── components
│ ├── Hello
│ ├── index.css
│ ├── index.jsx
index.jsx文件
import React,{Component} from "react";
import "./index.css"
export default class Hello extends Component{
render(){
return (
我是hello组件
)
}
}
是文件夹下面的文件都是组件名命名,同时后缀为jsx或者js
├── src
│ ├── components
│ ├── Wecome
│ ├── Wecome.css
│ ├── Wecome.jsx
Wecome.jsx文件
import React from "react";
import "./index.css"
class Hello extends React.Component{
render(){
return (
我是hello组件
)
}
}
export default Hello
import React,{Component} from 'react';
// 第一种写法,index可以省略
import Hello from "./components/Hello"
// 第二种写法,需要写全
import Wecome from "./components/Wecome/Wecome"
export default class App extends Component{
render(){
return (
)
}
}
对应的css文件需要加上一个.module
├── src
│ ├── components
│ ├── Hello
│ ├── Hello.module.css
│ ├── Hello.jsx
│ ├── Wecome
│ ├── Wecome.module.css
│ ├── Wecome.jsx
在引入的时候就可以使用样式模块化了,命名的话需要使用下面这种方式起名
import hello from "./Hello.module.css"
export default class Hello extends Component{
render(){
return (
我是hello组件
)
}
}
推荐插件
ES7+ React/Redux/React-Native snippets
拆分组件:拆分界面,抽离组件
实现静态组件:使组件实现静态效果
实现动态组件:动态显示初始化数据,数据的类型,数据名称,保存在那个组件
交互:绑定事件
传递参数
export default class App extends Component{
state = {
data:"我是父组件传递给子组件的参数"
}
render(){
return (
)
}
}
接受参数
export default class Hello extends Component{
render(){
console.log(this.props.data)
return (
我是Hello组件
)
}
}
接受参数
export default class App extends Component{
acceptHelloData = (data) => {
console.log("我是App组件,我接受到子组件Hello传递过来的数据为:",data)
}
render(){
return (
)
}
}
传递参数
export default class Hello extends Component{
handleBtnClick = () => {
const data = "我是Hello组件的数据"
this.props.handleBtnClick(data)
}
render(){
return (
)
}
}
在package.json中追加以下配置
"proxy":"http://localhost:5000"
优点就是配置简单,请求资源的时候不需要带上前缀
缺点就是不能配置多个代理
工作流程就是当你请求3000下面的资源不存在的时候就会走代理去请求5000下面的资源
先在src目录下面创建一个文件,名称是固定的
setupProxy.js
配置代理的代码
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function(app){
app.use(
createProxyMiddleware("/api1",{ //api1是需要转发的请求(所有带/api1的请求都转发给target路径目标地址)
target: "http://localhost:5000", //配置转发目标
changeOrigin: true, //控制服务器收到的host字段,false的时候会收到3000,true的时候会收到5000
pathRewrite: {"^/api1": ""} //必须配置,去除请求前缀
}),
createProxyMiddleware("/api2",{
target: "http://localhost:6000",
changeOrigin: true,
pathRewrite: {"^/api2": ""}
})
)
}
请求的时候,需要在端口号后面带上前缀
优点是可以配置多个代理
缺点就是配置繁琐,请求的时候如果需要走代理还需要加上前缀
npm install pubsub-js
第二步往入口文件index.js里面进行引入,然后往React身上挂载一个方法
// 引入消息订阅
import pubsub from 'pubsub-js'
// 往vm身上挂载一个消息订阅方法
React.pubsub = pubsub
接受数据,通过回调拿到数据,因为里面有两个参数,第一个为事件名称,第二个为数据,因此在这里就可以使用_来进行占位,在最后组件销毁之前记得取消这个订阅
componentDidMount() {
this.pid = this.pubsub.subscribe("hello",(_,data) => {
console.log("hello事件被触发了,触发者是:"+data)
})
}
componentWillUnmount() {
React.pubsub.unsubscribe(pid)
},
提供数据,调用publish身上的hello方法,然后传递数据
accept() {
React.pubsub.publish('hello','我是demo08传递过去的数据')
}
单页Web页面
整体就一个界面
点击页面中的链接不会刷新界面,只会做界面的局部刷新
数据通过接口进行获取之后,并在前端异步展示
单界面,多组件
一个路由就是一个映射关系(key: value)
key为路径,value可能是function或者component
后端路由
是key对应一个function
前端路由
是key对应一个component
react的一个插件库
专门用来实现一个SPA应用
基于react的项目基本都会用到该库
v5版本和v6版本来对比的话
内置组件的改变:移除了 ,新增了 等
语法的变化:component={About}变成了element={ }
新增了多个hook:useParams,useNavigate,useMatch等
也可以嵌套使用,且可以配合useRoutes()配置"路由表",但是需要通过 组件来渲染子路由
同时官方也明确了函数式组件
npm i [email protected]
to是要去的地址
About
使用activeClassName来指定选中的时候类名,然后高亮显示
About
用于注册路由,根据路径匹配,匹配到直接将对应的组件进行展示
path为路径是什么的时候,component是要渲染的组件
} />
BrowserRouter是路径后面没有/#
HashRouter是路径后面有/#,但是兼容性更强
// 先从react-router-dom身上引入所需要的BrowserRouter(地址中没有/#),HashRouter(地址中有/#,但是兼容性更好)
import { BrowserRouter,HashRouter } from "react-router-dom"
// 使用BrowserRouter标签包裹住App组件,使用路由
ReactDOM.render(
,
document.getElementById('root')
);
import React, { Component } from 'react'
import "./App.css"
// 通过按需引入所需要的标签
import { Link,Route,Routes } from "react-router-dom"
import Header from "./components/Header"
import About from "./pages/About"
import Home from "./pages/Home"
export default class App extends Component {
render() {
return (
{/* 切换路由标签,to是要去的地址 */}
About
Home
}/>
}/>
)
}
}
this.props.children
如果使用的话,那么在匹配同一个路径之后,匹配到之后还会再次往下进行匹配,当路由配置过多的时候就会影响效率
}/>
}/>
解决在public文件下下面的index.html文件里面引入css丢失的原因
原因分析,在多级路由的时候,例如/sanqi/home的时候,如果index.html里面引入css写的是./开头的,那么最终的查找地址之前就会带上一个/sanqi,当发现查找不到的时候,就会把public文件下的index.html进行返回
解决办法一
public文件夹下面的index.html中引入文件写法改变成/开头或者%PUBLIC_URL%/开头
解决办法二
可以使用hash的方式,这样就会把3000之后的全部进行省略掉
就是路由跳转标签,后面使用的时候,例如to的路径是/home/a/b,组件渲染时候对应的是home,那么此时也可以进行跳转的,它匹配上home之后你后面多带的内容它就忽略了,但是如果写成a/home/b就不可以,在对应渲染组件的标签上面写上exact={true}即可开启严格匹配,目前v6版本是都开启了严格模式,v6之前的版本需要自己进行开启,默认没有进行开启
当找不到对应路径的组件时候,渲染那个组件
即刚开始初始的样子
使用Redirect标签中的to来指定重定向的地址
import { Redirect } from "react-router-dom"
在有子路由的路由路径正常写就可以
}/>
子路由的跳转标签,to后面需要写/home/news,不可以直接写news
News
渲染组件的写法,path直接和上方to一样即可,直接写news,重定向需要写上/home,重定向的path是“”
} />
} />
传递参数的格式是对象格式,并且路径里面会显示参数
点击传递参数
{item.title}
渲染组件的时候进行接受
拿取数据,从this.props.match.params身上拿取所传递过来的参数
const { id,title } = this.props.match.params
传递参数的格式是urlencoded编码字符串,并且路径里面会显示参数
点击传递参数
{item.title}
渲染组件的时候进行接受
拿取数据,从this.props.location.search身上拿取所传递过来的参数,这个参数是一个urlencoded编码字符串,需要借助querystring进行解析
const { search } = this.props.location
传递参数的格式是对象格式,并且路径里面不会显示参数
点击传递参数
{item.title}
渲染组件的时候进行接受
拿取数据,从this.props.location.state身上拿取所传递过来的参数
const { id,title } = this.props.location.state
push会留下痕迹,repalce不会留下痕迹,会进行替换
{item.title}
replace跳转
this.props.history.replace("跳转地址")
replace跳转传递params参数
this.props.history.replace("跳转地址/参数一/参数二")
replace跳转传递serach参数
this.props.history.replace("跳转地址?id=参数一&title=参数二")
replace跳转传递state参数
this.props.history.replace("跳转地址",{id:"我是id",title:"我是title"})
push跳转
this.props.history.push("跳转地址")
push跳转传递params参数
this.props.history.push("跳转地址/参数一/参数二")
push跳转传递serach参数
this.props.history.push("跳转地址?id=参数一&title=参数二")
push跳转传递state参数
this.props.history.push("跳转地址",{id:"我是id",title:"我是title"})
前进
this.props.history.goForward()
后退
this.props.history.goBack()
go
指的是前进两个,负数的话就是后退
this.props.history.goBack(2)
负责加工一般组件,将路由组件上面的三个属性加工到一般组件,成为高阶组件
import { withRouter } from "react-router-dom"
class Header extends Component {
render() {
return (
头部一般组件
)
}
}
export default withRouter(Header)
实现路由的懒加载
import {lazy,Suspense} from "react"
const Home = lazy(() => import("./Home"))
const About = lazy(() => import("./About"))
路由懒加载...
npm i react-router-dom
需要写成一个回调函数,上来就会进行一次调用,往后每次点击的时候都会进行一次调用
根据三元运算表达式,来判断isActive,然后渲染对应的class类名,有当前activeItem类名的高亮显示
如果将来有一天,里面有子路由了,那么需要子路由亮的时候父路由不再亮,那么就需要加上一个end
isActive ? "navBoxItme activeItem" : "navBoxItme"} to="/about"/>About
path为路径是什么的时候,element是要渲染的组件
在外层需要包裹一个Routes标签,这个是必须的
}/>
如果使用的话,那么在匹配同一个路径之后,匹配到之后还会再次往下进行匹配,当路由配置过多的时候就会影响效率
在v6中是必须使用Routes这个标签来包裹所以的Route标签
}/>
}/>
只能在v6版本中进行使用,生成路由规则
将所有的路由抽离成一个js文件进行配置,方便对于路由的管理
先在src目录下面创建一个文件夹,然后下面创建一个index.js文件,里面内容如下,写好自己的路由规则,path为路径,element为要渲染的组件,包括重定向等等
import About from "../pages/About"
import Home from "../pages/Home"
import { Navigate } from "react-router-dom"
export default [
{
path:"/about",
element:
},
{
path:"/home",
element:
},
//路由重定向
{
path:"/",
element:
}
]
使用,在App.js文件里面写法
引入对于的路由表,使用useRoutes进行处理一下,将生成的结果放到指定位置即可
import React from 'react'
import { NavLink, useRoutes } from "react-router-dom"
import routes from "./routes"
export default function App() {
const routesElement = useRoutes(routes)
return (
About
Home
{routesElement}
)
}
当找不到对应路径的组件时候,渲染那个组件
即刚开始初始的样子
使用Route标签结合Navigate标签来指定重定向的地址
import { Route,Navigate } from "react-router-dom"
}>
嵌套路由可以写成嵌套的方式,里面的path就不需要加/了
} />
}>
} />
} />
}>
最后一步就是,在根标签的下级,写上标签,用于渲染子路由对应的组件
import { Outlet } from "react-router-dom"
子路由的跳转标签,to后面不需要写/home/news,直接写news即可,/home/news写法也是可以的,或者./news也可以
News
import About from "../pages/About"
import Home from "../pages/Home"
import Message from "../pages/Message"
import News from "../pages/News"
import { Navigate } from "react-router-dom"
export default [
{
path:"/about",
element:
},
{
path:"/home",
element: ,
children:[
{
path:"message",
element:
},
{
path:"news",
element:
}
]
},
{
path:"/",
element:
}
]
最后一步就是,在有子路由的jsx文件里面写上标签,用于渲染子路由对应的组件,占位的标签
import { Outlet } from "react-router-dom"
传递参数的格式是对象格式,并且路径里面会显示参数
点击传递参数
{item.title}
渲染组件的时候进行接受,在路由表中进行占位
{
path:"detail/:id/:title",
element:
}
函数组件,使用useParams来接收即可
import React from "react";
import { useParams } from "react-router-dom"
const Detail = () => {
const { id,title } = useParams();
return (
{id}
{title}
);
}
export default Detail
传递参数的格式是urlencoded编码字符串,并且路径里面会显示参数
点击传递参数
{item.title}
拿取数据,使用userSearchParams进行拿取到search,再通过search.get()进行获取参数
import React from "react";
import { useSearchParams } from "react-router-dom"
const Detail = () => {
const [search,setSearch] = useSearchParams();
const id = search.get("id")
const title = search.get("title")
return (
{id}
{title}
);
}
export default Detail
更新search参数
import React from "react";
import { useSearchParams } from "react-router-dom"
const Detail = () => {
const [search,setSearch] = useSearchParams();
const id = search.get("id")
const title = search.get("title")
return (
{id}
{title}
);
}
export default Detail
传递参数的格式是对象格式,并且路径里面不会显示参数
点击传递参数
{item.title}
拿取数据,使用useLocation进行拿取到state,再拿取对应的参数
import React from "react";
import { useLocation } from "react-router-dom"
const Detail = () => {
const {state{id,title}} = useLocation();
return (
{id}
{title}
);
}
export default Detail
统一使用useNavigate进行跳转,第一个是跳转的路径,第二个是配置对象,replace是否开启,传递参数只能传递state参数,还可以写成navigate(1)或者navigate(-1),一个前进一个后退
import React from "react";
import { useNavigate } from "react-router-dom"
const Detail = () => {
const navigate = useNavigate()
const {state{id,title}} = useLocation();
function skipHome(){
navigate("/home",{
replace:false,
state:{
message:"携带的消息"
}
})
}
return (
{id}
{title}
);
}
export default Detail
用于判断是否在路由的上下文中,是的话调用返回true,否则返回false,所有被App组件包裹的路由,都是true
import React from "react";
import { useInRouterContext } from "react-router-dom"
const Demo = () => {
console.log(useInRouterContext())
return (
Demo
);
}
export default Demo
用于返回当前导航的类型
POP,PUSH,REPLACE
POP指的是刷新
import React from "react";
import { useNavigationType } from "react-router-dom"
const Demo = () => {
console.log(useNavigationType())
return (
Demo
);
}
export default Demo
用于呈现当前组件中渲染的嵌套组件
如果里面的组件还没有进行渲染,就返回的是null
import React from "react";
import { useOutlet } from "react-router-dom"
const Demo = () => {
console.log(useOutlet())
return (
Demo
);
}
export default Demo
给一个值,帮你解析url中的path,search,hash值
import React from "react";
import { useResolvedPath } from "react-router-dom"
const Demo = () => {
console.log(useResolvedPath("/user?id=001&name=三七#qwe"))
return (
Demo
);
}
export default Demo
npm i antd
需要先引入样式,在src下面的index.js里面进行引入
import "antd/dist/antd.css"
其他参考官方文档进行使用
https://ant.design/components/button-cn
下载对应所需要的库
npm i @craco/craco
package.json修改配置
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
}
在根目录创建一个craco.config.js文件,进行配置按需引入即可
module.exports = {
// ...
};
redux是一个专门用于做状态管理的js库,不是react的库
它可以在react,vue,angular等项目中使用,但是配合最多的还是react
作用就是管理react应用中多个组件共享的状态
某个组件的状态也需要让其他组件可以随时拿到(共享)
一个组件需要改变另外一个组件的状态(通信)
能不用就不用,在可以不使用的情况下尽量不要使用
npm i redux
动作的对象
包含两个属性
type,标识属性,值为字符串,唯一 ,必要属性
data,数据属性,值类型任意,可选属性
用于初始化状态,加工状态
加工时,根据旧的state和action,产生新的state的纯函数
redux中的reducer函数必须是一个纯函数
去除组件本身的状态
src下建立redux文件夹,里面创建store.js和redecer.js
store.js里面引入redux中的createStore函数,使用该函数创建一个store
createStore调用的时候要传入一个为其服务的reducer
记得暴漏store对象
reducer.js
reducer的本质是一个函数,接收:preState,action,返回加工后的状态
reducer有两个作用:初始化状态,加工状态
reducer第一次调用的时候,是store自动触发的,传递preState是undefined
在index.js中检测store中状态的改变,一旦发生改变重新渲染App
redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
主要文件
// 当前这个文件用于暴漏store对象,整个应用只有一个store对象
// 从redux身上引入一个创建store的方法
import {createStore} from "redux"
// 引入为count组件服务的reducer
import countReducer from "./count_reducer"
// 创建store,并且暴漏出去
export default createStore(
countReducer
)
常量文件
// 该模块是用于定义type类型的常量值,在便于管理的同时也可以防止用户写错单词
export const INCREMENT = "increment"
export const DECREMENT = "decrement"
为count组件服务的reducer
// 该文件是用于创建一个为Count组件服务的reducer,reducer本质上就是一个函数
// reducer函数会接受两个参数,分别为:之前的状态(reducer),动作对象(action)
// 引入使用常量模块
import {
INCREMENT,
DECREMENT
} from "./constant.js"
// 初始化的时候preState是不传递参数的,此时是undefined,这个时候需要有一个默认值
// 然后进行导出
export default function countReducer(preState = 0, action) {
// console.log(preState,action)
// 通过解构拿到当前动作(type)和数据(data)
const { type, data } = action;
// 判断当前动作对象
switch (type) {
case INCREMENT:
// console.log("increment执行了")
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
为count组件服务的action
// 该文件是生成为Count组件服务的action对象
// 引入使用常量模块
import {
INCREMENT,
DECREMENT
} from "./constant.js"
// 定义加的action
export const createIncrementAction = data => (
{
type:INCREMENT,
data
}
)
// 定义减的action
export const createDecrementAction = data => (
{
type:DECREMENT,
data
}
)
在入口文件里面进行监听store改变
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 引入store
import store from "./redux/store"
// 开启所有的监听,只要更改,执教将整个app进行从新渲染调用render函数,不用担心效率的问题,底层有diff算法
store.subscribe(() => {
ReactDOM.render(
,
document.getElementById('root')
);
})
延迟的动作不想交给组件自身,想交给action
何时需要用到action,想对状态进行改变,但是不知道具体怎么改变,需要等具体数据回来之后再进行任务
先安装thunk,并且配置在store中
npm i redux-thunk
创建的action函数不再返回一个对象了,而是一个函数,在这个函数里面写异步任务
异步任务执行完毕之后,使用同步action去真正的操作数据
异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
主要文件
// 当前这个文件用于暴漏store对象,整个应用只有一个store对象
// 从redux身上引入一个创建store的方法
// applyMiddleware是用来处理action异步任务的
import {createStore,applyMiddleware} from "redux"
// 引入为count组件服务的reducer
import countReducer from "./count_reducer"
// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"
// 创建store,并且暴漏出去
export default createStore(
countReducer,
applyMiddleware(thunk)
)
为count组件服务的action
// 该文件是生成为Count组件服务的action对象
// 引入使用常量模块
import {
INCREMENT,
DECREMENT
} from "./constant.js"
// 定义加的action
export const createIncrementAction = data => (
{
type:INCREMENT,
data
}
)
// 定义减的action
export const createDecrementAction = data => (
{
type:DECREMENT,
data
}
)
// 定义加的异步action,里面需要调用到同步action
export const createIncrementAsyncAction = (data,time) => {
return (dispatch) => {
// store只能接受到一个对象,然后交给手下reducer进行处理,但是此时交给store的是一个函数
// 我们就需要使用到一个第三方的库thunk来进行处理
// 安装 npm i redux-thunk
setTimeout(() => {
console.log("通过thunk进行处理的异步action")
dispatch(createIncrementAction(data))
}, time);
}
}
npm i react-redux
之前的组件现在被拆分成了UI组件和容器组件,容器组件包裹着UI组件,UI组件只负责展示界面和逻辑交互,并不会和redux进行打交道,全部是由容器组件和redux进行打交到
首先创建一个文件夹containers,里面存放的都是UI组件的容器组件
需要引入对应的容器组件,并且把需要用到的store传递进去
import React, { Component } from 'react'
// 引入容器组件
import Count from "./containers/Count"
// 引入store
import store from "./redux/store"
export default class App extends Component {
render() {
return (
{/* 渲染容器组件并且传递过去store */}
)
}
}
// 该文件是Count的容器组件,用于链接react-redux和Count的UI组件
// 引入Count的UI组件
import CountUI from "../../components/Count"
// 引入连接UI组件与redux
import {connect} from "react-redux"
// 引入创建的action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from "../../redux/count_action"
// 创建一个函数,返回的是一个对象
// 返回的对象中key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
// 该函数用于传递状态
function mapStateToProps(state){
return {
count: state
}
}
// 创建一个mapDispatchToProps函数,返回的是一个对象
// 返回的对象中key就作为传递给UI组件的props的key,value就作为传递给UI组件props的value
// 该函数用于传递修改状态的方法
function mapDispatchToProps(dispatch){
return {
increment: number => dispatch(createIncrementAction(number)),
decrement: number => dispatch(createDecrementAction(number)),
incrementAsync: (number,time) => dispatch(createIncrementAsyncAction(number,time))
}
}
//最后使用connect连接UI组件和容器组件,并且导出
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
对应的UI组件使用props就可以使用容器组件传递过来的redux状态和修改redux状态的方法
import React, { Component } from 'react'
import "./index.css"
export default class Count extends Component {
state = {
name:"三七"
}
increment = () => {
const { value } = this.selectNumber
this.props.increment(value * 1)
}
decrement = () => {
const { value } = this.selectNumber
this.props.decrement(value * 1)
}
incrementIfOdd = () => {
const { value } = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.increment(value * 1)
}
}
incrementAsync = () => {
const { value } = this.selectNumber
this.props.incrementAsync(value * 1,1000)
}
render() {
return (
当前用户为:{this.state.name},当前求和为:{this.props.count}
)
}
}
从最开始的创建两个函数,返回对应的状态和方法,在传入到connect中,简写成直接在connect中写
状态的传递还是使用函数,方法的传递有函数和对象两种方式传递
// 该文件是Count的容器组件,用于链接react-redux和Count的UI组件
// 引入Count的UI组件
import CountUI from "../../components/Count"
// 引入连接UI组件与redux
import {connect} from "react-redux"
// 引入创建的action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from "../../redux/count_action"
// 简写方式,第一个写成一个函数返回对应的状态,第二个也是一个函数返回对应操作状态的方法,也可以进行简写成一个对象
// 第二个简写成对象,因为使用了react-redux,会自动帮助你调用dispatch,不需要自己手动写了
export default connect(
state => ({
count: state
}),
// 完整写法
// dispatch => ({
// increment: number => dispatch(createIncrementAction(number)),
// decrement: number => dispatch(createDecrementAction(number)),
// incrementAsync: (number,time) => dispatch(createIncrementAsyncAction(number,time))
// }),
// 简写
{
increment: createIncrementAction,
decrement: createDecrementAction,
incrementAsync: createIncrementAsyncAction
}
)(CountUI)
不需要使用store.subscribe来进行监听redux状态的改变了,同时App.jsx文件里面的store,在这里使用react-redux中的Provider标签包裹整个App,进行传递store
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 引入store
import store from "./redux/store"
// 引入react-redux的Provider
import { Provider } from 'react-redux'
ReactDOM.render(
,
document.getElementById('root')
);
主要就是使用react-redux的时候,需要定义一个UI组件还有一个容器组件,此时就定义了两个文件,当组件比较多的时候就成倍增长文件,比较麻烦,因此可以定义在一个文件里面,主要的实现思路就是不在容器组件里面引入UI组件,而是直接将UI组件写在容器组件的文件里面,然后进行连接redux
此时components文件里面的CountUI组件就可以进行删除了,在containers文件下面的容器组件里面写UI组件即可
import React, { Component } from 'react'
//引入样式
import "./index.css"
// 引入连接UI组件与redux
import {connect} from "react-redux"
// 引入创建的action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from "../../redux/count_action"
// 定义UI组件
class Count extends Component {
state = {
name:"三七"
}
increment = () => {
const { value } = this.selectNumber
this.props.increment(value * 1)
}
decrement = () => {
const { value } = this.selectNumber
this.props.decrement(value * 1)
}
incrementIfOdd = () => {
const { value } = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.increment(value * 1)
}
}
incrementAsync = () => {
const { value } = this.selectNumber
this.props.incrementAsync(value * 1,1000)
}
render() {
return (
当前用户为:{this.state.name},当前求和为:{this.props.count}
)
}
}
//容器组件并且链接UI组件
export default connect(
//状态
state => ({
count: state
}),
//修改状态的action
{
increment: createIncrementAction,
decrement: createDecrementAction,
incrementAsync: createIncrementAsyncAction
}
)(Count)
import {createStore,applyMiddleware,combineReducers} from "redux"
// 引入为count组件服务的reducer
import countReducer from "./reducers/count"
// 引入为count组件服务的reducer
import personReducer from "./reducers/person"
//进行汇总所有的reducer
const allReducer = combineReducers({
countReducer,
personReducer
})
// 创建store,并且暴漏出去
export default createStore(
allReducer
)
使用的时候取状态
export default connect(
state => ({
userObjList: state.personReducer,
count: state.countReducer
}),
{
addUserObj: createAdduserAction
}
)(Person)
下载
npm i redux-devtools-extension
使用
import {composeWithDevTools} from "redux-devtools-extension"
export default connect(allReducer,composeWithDevTools(applyMiddleware(thunk)))(CountUI)
通过命令行进行打包项目
npm run build