React01-yarn包管理器
React01-yarn包管理器
安装Nodejsd的默认会给你们安装包管理器npm
介绍yarn包管理器,这个包管理器和npm一样,都是可以去下载对应依赖
npm包管理器缺陷:
-
npm的下载比较慢,镜像默认是国外的镜像,我们国内可以配置淘宝镜像。
-
同一项目我们不同时间下载依赖,可能遇到包版本不一致的情况。经常下载依赖过后,版本
~5.0.3
表示你们安装的版本5.0.x版本。这个x值是一个可变的值。 -
下载报错问题,npm进行包管理下载的时候,同一时间下载多个包,其中某一个包出现下载报错。其他包会正常继续下载。某个包出现问题,项目跑不起来。
yarn包管理器特点:
在react中经常会使用的一个包管理器,官网也推荐我们用yarn包管理器
-
快速性:下载依赖也可以配置国内镜像,下载过的每个包都可以被缓存起来,下次就不用再重新下载,直接从缓存里面获取
-
安全性:在执行代码之前,yarn包管理器会根据一些算法验证这个包是否安全,完整性验证
-
并行安装:如果同时下载很多任务,并安装到本地,我们并行执行
-
安装版本统一:yarn来下载我们包,版本号是固定的。在我们项目中yarn.lock这个文件中就会将版本锁定
-
命令比较简单:相对于npm来说,yarn的命令比较由语义
一、下载yarn
目前每个同学的电脑环境都不一样,有的win7、win10、win11.保证每个同学的电脑都能正常使用yarn
yarn的官方文档:https://yarn.bootcss.com
npm install yarn -g
检测版本
yarn --version
使用命令查看yarn的配置
yarn config list
yarn的淘宝镜像配置
yarn config set registry https://registry.npm.taobao.org -g yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g
node-sass下载到本地很慢,甚至可能下载失败
二、使用命令
-
初始化项目
yarn init --y
你们项目中就会出现一个package.json文件。这个文件里面存放依赖信息
-
添加依赖
yarn add 包名 yarn add 包名@版本号
比如:
yarn add jquery yarn add vue@2.6.10
添加开发依赖
yarn add webpack --dev
--dev
代表这个包开发依赖,开发环境下可以使用,打包后生成环境不需要将这个依赖打包过去dependencies
存放的包都是生成环境需要依赖。devDependencies
: 开发依赖,这个包只在你们开发过程中有用,项目打包后就不需要这个依赖。不打包最后生产环境中 -
删除依赖
yarn remove jquery yarn remove jquery@版本
npm 命令 | yarn 命令 |
---|---|
npm install | yarn add/yarn install |
npm install [package] —save | yarn add [package] |
npm install [package] —save-dev | yarn add [package] —dev |
npm install package(https://github.com/1).1.1 —save | yarn add package(https://github.com/1).1.1 |
npm uninstall [package] —save(-dev) | yarn remove [package] |
npm update —save | yarn upgrade |
npm init | yarn init |
React02-React介绍和安装
React02-React介绍和安装
一、React基本概念
学习一个新的框架,一个新的技术
React官网:https://reactjs.org
React中文网站:https://react.docschina.org
React项目起源于facebook内部,2013年5月开源的一个前端框架。起初是facebook是为了公司内部研发一个产品,但是发现市面上所有的框架都不好用,他们自己研发React框架
React有两条路线:React本身用于开发PC端页面
React-Native:使用React来开发移动端,主要android、ios
优势:
-
技术成熟、社区完善、配件齐全。适合于中大型项目设计
-
由facebook专门团队来维护和更新产品。产品稳定性和质量可靠
-
开发非常灵活,在项目中可发挥的余地比较大
Vue和React这两个框架:
Vue是高度封装的一个前端框架,自动档
React是一个基于JS逻辑来开发业务的一个前端框架、手动挡
在React中没有路由守卫,我们自己写一个路由。
官方将React称为一个JS库?
官方只跟我们提供的React基础内容。没有路由、状态机
React框架(React全家桶=React官方基础+第三方路由+第三方状态机)
二、搭建项目
目前React最新的版本是18
我们一般常见项目
npx create-react-app 项目名字
这是目前官方推荐的安装项目方式
不用在本地安装脚手架,临时在本地搭建一个脚手架,项目创建完毕后删除脚手架
可以保证创建项目脚手架是最新
创建项目的时候,你们项目名字不要由大写字母,全小写。如果遇到多个单词你们可以用-
或者_
my-projectmy_project
创建项目成功后
Success! Created my-project at C:\Users\xuchaobo\Desktop\21期班级\第五阶段\Code\my-project Inside that directory, you can run several commands: npm start Starts the development server. npm run build Bundles the app into static files for production. npm test Starts the test runner. npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd my-project npm start Happy hacking!
提示你要执行两个命令
cd my-project
然后启动项目
npm start yarn start
目前React脚手架要求最低的node版本是14以上的。
React03-JSX概念
React03-JSX概念
一、什么是JSX
JSX=JavaScript+XML
JSX可以说是JS的一种扩展语法,他就是将JavaScript和XML代码进行结合(XML也是一种标签语法)
HTML:超文本标记语言,重在显示
XML:可扩展的标记语言,重在传输
两则都是ML标记语言,你们在JSX中写代码不是HTML代码,实际上XML。但是都是标记语言,理解写的HTML代码也可以
JSX就是一种JS封装,好处就是JS中直接嵌入HTML模板代码。最后交给我们脚手架工具,脚手架工具默认将JSX代码编译为JS代码,拿到浏览器运行
JSX特点:
-
JSX执行更快,因为他在编译的时候就可以进行优化
-
JSX类型安全的,编译过程中如果遇到问题,抛出错误。编译过程
-
JSX写起来会比我们纯JS更加简单
二、插件安装
React组件有两种开发方式
用类来开发组件:要用面向对象 快捷键:RCC
用函数来开发组件:只需要会定义函数就可以 快捷键:RFC
目前暂时推荐大家用函数组件
三、基础表达式
import React from 'react' export default function JSXDemo() { // 定义数据 const username = "xiaowang" const number1 = 20 const number2 = 30 const age = 18 const user = { username:"bobo" } // 定义一个函数 const show = (val)=>{ return val.toUpperCase() } const array = [1,3,5,8] const array2 = [ {id:1,name:"xiaowang"}, {id:2,name:"xiaofeifei"}, ] const array3 = [ <li key="1">1</li>, <li key="2">2</li>, <li key="3">3</li> ] const array4 = array2.map(item=>{ return <li key={item.id}>{item.id}--{item.name}</li> }) // [<li key=1>xiaowang</li>,<li key=2>xiaofeifei</li>] return ( <div> <h3>JSX学习</h3> {/* 基础的语法 */} <p>{username}</p> <p>{number1 + number2}</p> {/* 条件运算 */} <p>{age>=18?"成年":"未成年"}</p> {/* 对象表达式,不能直接输出对象 */} <p>{user.username}</p> {/* 函数表达式,类似于Vue过滤器 */} <p>{show("abcd")}</p> {/* 数组表达式 */} {/* React的模板,默认将数组里面输出进行遍历 */} <p>{array}</p> {/* <p>{array2}</p> */} <ul> {array3} </ul> <ol> {array4} </ol> </div> ) }
四、JSX中的样式
JSX 支持两种样式
-
行内样式:直接在模板上面添加style属性,里面写css样式代码。
JSX将所有的css代码都做了封装,必须要使用JSX封装好的css代码
import React from 'react' export default function JSXStyle() { return ( <div> <h3> JSXStyle </h3> <p style={{color:"red",fontSize:"20px",border:"1px solid red",borderTopLeftRadius:"10px"}}>蜗牛学院</p> </div> ) }
目前JSX代码中行内样式都被封装,以前font-size变成fontSize了
-
外部样式
行内样式我们不好维护,我们可以将css样式提取到外部去
引入到组件中使用
你可以项目assets目录下面创建style文件夹,以后所有的css样式都在放这个文件夹里面
home.css 文件
.active{ color:green } .as{ width: 100px; height: 100px; background-color: red; }
在组件中引入外部样式
import React from 'react' import "../assets/styles/home.css" export default function JSXStyle() { return ( <div> <h3> JSXStyle </h3> <p style={{color:"red",fontSize:"20px",border:"1px solid red",borderTopLeftRadius:"10px"}}>蜗牛学院</p> {/* 要给div增加class属性,不能写class,必须要className */} {/* 注释的快捷键:ctrl+/ */} <div className='as'> <span className='active'>测试</span> </div> </div> ) }
React04-组件开发
React04-组件开发
React中组件也是将UI部分进行拆分。
React组件功能层面会分类两类:
-
UI组件:专注于视图的显示渲染
-
容器组件:专注于业务的实现
在开发过程中我们并没有强调UI组件何容器组件之间划分。一般在一个组件中既有UI部分渲染,也有业务逻辑数据定义。
一、组件分类
React从组件类型(技术层面)会分类两类
-
类组件:采用面向对象的语法来开发
-
函数组件:采用函数式编程来进行开发
在学习过程中需要区分
一个项目既有类组件,又有函数组件,这两类组件我们都必须掌握。
区别:
-
类组件开发需要面向对象基础知识点。ES6面向对象基础语法。有状态(响应式数据)组件
-
函数组件开发采用函数式编程,相对来说简单一些。无状态的组件。学习hooks编程
二、创建类组件
创建类组件,我们需要在页面上RCC来动态生成模板
import React, { Component } from 'react' export default class Header extends Component { render() { return ( <div>Header</div> ) } }
特点:
-
子类必须要继承一个Component,才能称为组件
-
需要在组件中重写render方法,这个方法就是页面渲染的节点代码
-
需要将当前这个组件暴露出去
三、创建函数组件
import React from 'react' export default function Content() { return ( <div>Content</div> ) }
函数组件相对来说要简单一些。只需要暴露一个函数,里面returnJSX代码就可以了
但是函数组件也有缺陷:函数组件是无状态的组件,内部的数据无法响应式变化
四、组件中使用图片
网络图片跟以前一样,直接引入地址就可使用
本地图片不能直接引入,因为我们在本地存放assets目录,打包后不存在。
打包后图片、css、js代码会默认放在static目录下面。
页面中本地图片找不到图片路径
语法一:
import React, { Component } from 'react' import wechat from "../../assets/images/wechat.jpg" export default class Picture extends Component { render() { return ( <div> <h3>Picture</h3> <img src={wechat} alt="" /> </div> ) } }
第一种方式就是将图片采用模块化的方式引入进来。打包的时候,默认找你们打包后的图片路径。
import React, { Component } from 'react' import wechat from "../../assets/images/wechat.jpg" // 后端模块化 const img = require("../../assets/images/wechat.jpg") export default class Picture extends Component { render() { return ( <div> <h3>Picture</h3> <img src="//m15.360buyimg.com/mobilecms/jfs/t1/193777/22/26134/133637/62aefc49Ea2826884/3fbbc8aa39cbc4de.jpg!cr_1125x449_0_166!q70.jpg" alt="" /> <img src={wechat} alt="" /> <img src={img} alt="" /> <img src={require("../../assets/images/wechat.jpg")} alt="" /> </div> ) } }
通过require可以在图片加载的时候,引入这个图片资源。
本地图片用的少,一般都是网络图片
css代码中需要使用本地图片
外部的css直接引入本地图片就可以加载成功
标签上面的样式,我们使用style需要动态引入
<div style={{width:"100px",height:"100px",background:`url(${require('../../assets/images/wechat.jpg')})`}}></div>
总结:只要JSX标签里面要使用本地图片,考虑动态引入图片。
五、组件中样式
JSX中允许两种样式
-
内部样式:style标签
-
外部样式:引入的时候,组件之间会相互影响
样式穿透:在React中样式穿透全局影响。
样式模块化
我们需要将样式设置为模块化,你在哪个组件中引入这个样式,只能在这个组件作用
没有模块化,css外部样式就是全局的样式
(1)在设计样式文件名字,必须满足下面规则
index.css——>index.module.css
(2)在组件中引入的时候,必须模块化引入
// import "../../assets/styles/index.module.css" import IndexStyle from "../../assets/styles/index.module.css"
(3)在使用的时候,必须指定从哪个模块获取css样式
export default function StyleDemo01() { return ( <div> <h3>StyleDemo01</h3> <div className={IndexStyle.box1}></div> </div> ) }
完整的代码
import React from 'react' // module,这个css必须模块化引入静态引入 // import "../../assets/styles/index.module.css" import IndexStyle from "../../assets/styles/index.module.css" export default function StyleDemo01() { return ( <div> <h3>StyleDemo01</h3> <div className={IndexStyle.box1}> <span>woniu</span> <p className={IndexStyle.op}>xueyuan</p> <p id={IndexStyle.op1}>孵化园</p> </div> </div> ) }
六、组件中的事件
React要使用事件,不是在原生事件,React中所有事件都已经被封装。
JSX提供的CSS封装过后的css 。font-size—->fontSize
JSX提供的事件也是封装过后的事件。onclick—->onClick
onclick属于原生DOM事件,浏览器都能识别。
onClick属于合成事件(JSX封装过后事件)。是React已经处理过的事件
(1)事件类型
在dom中直接绑定事件
odiv.onclick = function(){} osel.onchange = function(){} radio.onchange input.onblur = function(){}
dom事件是直接绑定到dom身上,主要dom原生触发这个动作,执行你们事件函数
不是onclick才有事件,事件一直都有,onclick监听这个动作
在React中事件称为合成事件,不是直接绑定在dom身上。以后再JSX中给一个原生绑定onClick,默认不是再dom原生身上绑定这个事件。(合成事件原理?)
onclick ===> onClick onchange ===> onChange onblur ===> onBlur
事件名字发生变化,采用驼峰命名的方式
基础代码
import React, { Component } from 'react' export default class EventComp extends Component { play() { console.log(123); } show(){ console.log(234); } render() { return ( <div> <h3>EventComp</h3> <button onClick={this.play}>修改</button> <input onBlur={this.show} type="text" /> </div> ) } }
(2) this指向的问题
import React, { Component } from 'react' export default class EventComp extends Component { play() { console.log("正在玩耍"); } show(){ // React不能直接让你获取当前节点,默认给你undefined console.log(this); this.play() } render() { return ( <div> <h3>EventComp</h3> <button onClick={this.play}>修改</button> <button onClick={this.show.bind(this)}>show</button> <input onBlur={this.show} type="text" /> </div> ) } }
如果你组件中定义的事件函数是普通函数,默认获取到this值为undefined。React默认不允许直接获取节点
绑定事件是普通函数,改变this指向当前组件
<button onClick={this.show.bind(this)}>show</button>
bind默认返回一个函数。onClick={函数}
箭头函数下面就不存在this执行错误的情况
import React, { Component } from 'react' export default class EventComp extends Component { message = ()=>{ console.log(this); } render() { return ( <div> <h3>EventComp</h3> <button onClick={this.message}>箭头函数</button> </div> ) } }
推荐大家以后再定义事件函数的时候,必须用箭头函数来实现。你也可以不用箭头函数,必须搞清楚this指向
面试题:你说一下React中事件的特点?
React中的事件并不是直接绑定DOM原生身上,合成事件(?)
React中事件绑定,如果普通函数,this默认执行undefined,如果是箭头函数this默认执行当前组件
(3)阻止事件默认行为
(阻止冒泡)e.stopPropagation()
deleteTopNav = (e) =>{ e.stopPropagation() } <p onClick={(e)=>{this.deleteTopNav(e)}}>X</p>
(4)事件委托
changeStatus = (event) => { const id = event.target.getAttribute("index") }; <ul onClick={changeStatus}> {filterArr.map((item) => { return ( <li key={item.id} index={item.id} className={item.status ? Styles.done : ""} > {item.task} </li> ); })} </ul>
React05-组件内部state
React05-组件内部state
一、回顾Vue
回顾:组件的数据由两部分组成
-
内部数据,比如data里面定义的数据组件自己内部数据
-
外部数据,props来接受到的数据都是外部数据
不管是内部数据还是外部数据,只要发生变化页面都会响应式变化。
React的数据由两部分组成
-
内部数据,组件内部自己定义的数据,必须按照要求来定义,才能称为内部数据
-
内部外部数据,父组件传递过来的数据
二、内部状态
内部的state数据
React内部数据的定义我们必须使用一个变量state来表示
(1)定义数据
export default class ListComp extends Component { constructor() { super() this.state = { list:[ { id: 1, name: "xiaoawng", age: 17 }, { id: 2, name: "xiaofei", age: 18 }, { id: 3, name: "xiaozhang", age: 20 }, ] } }
我们可以在构造函数里面自己写一个state属性(必须是这个属性),在里面定义好你的数据
import React, { Component } from 'react' export default class ListComp extends Component { state = { list: [ { id: 1, name: "xiaoawng", age: 17 }, { id: 2, name: "xiaofei", age: 18 }, { id: 3, name: "xiaozhang", age: 20 }, ] }
这个state就相当于Vue中data。里面数据能够实现响应式变化
(2)数据的获取
<tbody> { this.state.list.map(item => { return ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.age}</td> <td> <button onClick={() => this.deleteRow(item.id)}>删除</button> </td> </tr> ) }) } </tbody>
一般不建议你们直接在模板代码中this.state.list
来获取数据
render() { const {list} = this.state return ( <div> <h3>ListComp</h3> <table border="1"> <thead> <tr> <th>编号</th> <th>名字</th> <th>年龄</th> <th>操作</th> </tr> </thead> <tbody> { list.map(item => { return ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.age}</td> <td> <button onClick={() => this.deleteRow(item.id)}>删除</button> </td> </tr> ) }) } </tbody> </table> </div> ) }
建议我们在开发过程中,先render方法里面讲需要的数据结构赋值出来。
以后页面中可以重复使用,不要频繁操作this.state
(3)修改数据
在react组件中我们要修改数据,必须使用setState
这个函数来执行
直接修改state的数据,页面并不能更新,render不会被触发,
你调用setState,一旦这个方法执行完毕后,默认底层调用render,页面就能实现动态渲染
this.setState({ list:新的list })
实际代码
deleteRow = (id) => { this.setState({ list: this.state.list.filter(item => item.id != id), username:"xiaofeifei" }) }
练习:
定义一个数组,数组里存放user对象
在页面动态渲染出对象。
点击删除后删除指定的对象
三、state的特点
异步更新
我们在调用setState这个函数的时候,默认是异步更新。
Vue的更新过程也是异步的过程。
更新过程需要消耗比较多的时候,同步代码。页面在加载等待这个结果
changeUsername = ()=>{ this.setState({ username:"夏青松" }) console.log(this.state.username); }
如果更新后我们需要拿到更新的结果,可以传递第二个参数
changeUsername = ()=>{ this.setState({ username:"夏青松" },()=>{ console.log(this.state.username); }) }
异步更新,更新完毕后默认调用回调函数。
合并我们操作
当我们在一个函数中执行多次setState数据更新。默认将多次操作合并在意思
changeUsername = ()=>{ // this.setState({ // username:"夏青松" // },()=>{ // console.log(this.state.username); // }) this.setState({ username:"夏青松" }) this.setState({ username:"夏青松" }) }
render方法最后只会执行一次。底层对你们的setState操作进行合并。最终渲染一次
底层:当我们检测到同一个任务中执行多个seState,底层提供一个队列。将你们修改数据的操作默认放在队列里面排队。所以底层更新他是异步的。
在放入队列之前,判断你们的操作是否有重复,是否需要合并,有重复操作踢出重复过程,如果有多个修改任务,将修改采用Object.assign方法合并在一起,在加入队列中等待异步修改。
底层做的性能优化
强制更新
React其实也提供了一个强制更新的api
changeUsername = ()=>{ // this.setState({ // username:"夏青松" // },()=>{ // console.log(this.state.username); // }) // this.setState({ // username:"夏青松" // }) // this.setState({ // username:"夏青松" // }) // this.setState({ // password:"333" // }) this.state.username = "徐豪" // 强制更新,强行让底层调用render方法 // Vue中也有这个函数,this.$fouceUpdate() this.forceUpdate() }
当你们页面发生数据变化,没有调用setState的时候,我们可以强制让React调用render方法
尽量少用。
React06-组件通信
React06-组件通信
一、父子组件通信
在React中我们也需要完成父子组件通信。
一个组件的数据来源两部分:
-
组件内部state
-
组件外部的props
二、父传子
在父组件定义state内部数据,将这个数据动态传递给子组件
import React, { Component } from 'react' import ChildrenComp from './ChildrenComp' export default class ParentComp extends Component { state = { username:"xiaowang" } changeUsername = ()=>{ this.setState({ username:"xiaozhang" }) } render() { const {username} = this.state return ( <div> <h3>ParentComp</h3> <p>{username}</p> <button onClick={this.changeUsername}>修改username</button> <ChildrenComp username={username} age 10"></ChildrenComp> </div> ) } }
在子组件上面你们可以传递动态属性,也可以传递静态的属性值。
父组件更新了state数据,传递给子组件自动更新
参数传递的一些技巧
<ChildrenComp {...user} username={username} age={true}></ChildrenComp> </div>
在传递数据给子组件的时候,参数树动态的,区分值是数字还是布尔值,确认是否是state里面的变量
三、子组件接收
子组件要接受外部的数据,需要通过props来接受
在类组件中,props默认挂载到this对象
如果输出this这个变量,返回一个对象,这个对象包含(props、state、context)
props就是代表组件外部数据
import React, { Component } from 'react' export default class ChildrenComp extends Component { render() { const {username,age} = this.props console.log(this.props); return ( <div> <h4>ChildrenComp</h4> <p>{username}</p> <p>{age}</p> <p>{typeof age}</p> {/* <p>{user.address}</p> */} </div> ) } }
第二种方式可以在构造器里面获取
export default class ChildrenComp extends Component { constructor(props){ super() console.log(props); } render() { return ( <div> <h4>ChildrenComp</h4> </div> ) } }
只要你是React类组件,我们都可以在构造函数里面接受props值,这个值是创建这个组件的时候,默认传递进来的。只是这种方式写起来更麻烦,平时不会这样写。
四、验证器
父组件传递参数给子组件。传递的参数有限制。主要类型限制
在React17版本之前,验证器需要自己下载。但是在目前版本中验证器无需下载
yarn add prop-types //目前我们react18无需下载
子组件中加入验证器
import React, { Component } from 'react' import PropTypes from "prop-types" export default class ChildrenComp extends Component { render() { const {username,age,stu} = this.props return ( <div> <h4>ChildrenComp</h4> <p>{username}</p> <p>{age}</p> <p>{stu}</p> </div> ) } } // 验证的代码需要写在组件外部 // 要验证我们组件内部props的属性。需要获取propTypes变量 ChildrenComp.propTypes = { // 验证username必须是string类型,必须要提供 username:PropTypes.string.isRequired, age:PropTypes.number.isRequired, stu:PropTypes.array.isRequired }
PropTypes直接引入就可以,后面接着数据类型。在接着约束
验证失败,在控制台抛出警告
五、默认值
默认值的生效环境,父组件没有传递对应的值进来
import React, { Component } from 'react' import PropTypes from "prop-types" export default class ChildrenComp extends Component { render() { const {username,age,stu} = this.props return ( <div> <h4>ChildrenComp</h4> <p>{username}</p> <p>{age}</p> <p>{stu}</p> </div> ) } } ChildrenComp.defaultProps = { stu:[1,2] }
当外部没有传递stu属性的时候,子组件默认给页面一个数组
回顾:Vue中props验证和默认值
export default { props:["username"], props:{ username:{ type:Array, default:[] } } data(){ return{ } } }
六、单向数据流
父组件传递值给子组件,不能修改传递过来props的值
目的:保证我们开发过程中数据流方向是可控的。不要因为子组件影响了整个程序数据流方向。造成难以维护数据
import React, { Component } from 'react' import PropTypes from "prop-types" export default class ChildrenComp extends Component { updateUsername = () => { this.props.username = "xiaofeifei" } render() { const { username, age, stu } = this.props return ( <div> <h4>ChildrenComp</h4> <p>{username}</p> <p>{age}</p> <p>{stu}</p> <button onClick={this.updateUsername}> 修改username</button> </div> ) } }
props里面提供的是一个对象,修改这个对象里面的数据可以监控不到发生变化。
七、子传父
子组件必须要调用父组件的修改函数来执行数据修改,子组件在继续更新
父组件那边定义修改函数
import React, { Component } from 'react' import ChildrenComp from './ChildrenComp' export default class ParentComp extends Component { state = { username:"xiaowang" } changeUsername = (val)=>{ this.setState({ username:val }) } render() { const {username,students,user} = this.state return ( <div> <h3>ParentComp</h3> <ChildrenComp changeMyName={this.changeUsername} username={username}></ChildrenComp> </div> ) } }
子组件通过获取到回调函数
import React, { Component } from 'react' import PropTypes from "prop-types" export default class ChildrenComp extends Component { //子组件调用父组件的函数 changeParentData = ()=>{ console.log(this.props); this.props.changeMyName("xiaofafa") } render() { const { username} = this.props return ( <div> <h4>ChildrenComp</h4> <p>{username}</p> <button onClick={this.changeParentData}>修改数据</button> </div> ) } }
八、事件总线
了解
九、计算属性
在React中,类组件默认没有像Vue专门提供一个计算属性的模块。而是采用ES6的get来进行计算属性
get total(){ const {shopCar} = this.state return shopCar.reduce((sum,obj)=>{ return sum+=obj.price * obj.number },0) }
total在这里通过get的方式来进行定义,其实就是给当前这个组件设置一个属性。
get来定义这个total的好处,total里面可以写业务逻辑,依赖的数据一旦发生变化,传递给子组件的数据会动态计算一次。
class Dog{ constructor(color){ this.color = color } get name(){ return "xiaohuahua" } }
计算属性在调用的时候,可以直接使用this.total
export default class Car extends Component { render() { return ( <div className={ShopCarStyle.carContent}> <h3>购物车</h3> <p>总价为:{this.total}</p> </div> ) } }
React07-表单组件
React07-表单组件
React表单组件操作和Vue区别很大
Vue中操作表单元素
-
v-model双向绑定直接获取到表单的数据
-
ref的获取表单节点, $refs来得到这个节点。获取数据
<input ref="username"> this.$refs ===> ["username"] this.$refs.username =>input节点
在React中没有双向绑定点概念。我们需要获取到表单元素的值,提供了两种方式
-
受控组件
-
非受控组件
一、受控组件
在React中input\textarea\select这些都是表单组件。
我们如果要获取表单组件的值,可以通过受控组件的概念完成
受控组件:表单元素的value值跟state内部状态产生了关联。以后我们state的值发生变化表单里显示内容会动态变化
import React, { Component } from 'react' export default class ControllerComp extends Component { state = { username:"xiaowang" } render() { const {username} = this.state return ( <div> <h3>ControllerComp</h3> <input type="text" value={username} /> </div> ) } }
特点:
-
表单的值和state已经绑定在一起了,以后state里面数据发生变化,表单的值就会动态变化
-
页面上表单无法输入任何内容。value属性已经被state控制了,不能让你输入内容
-
你只要绑定value属性,我们就必须在给当前组件绑定onChange事件
完成双向绑定过程
import React, { Component } from 'react' export default class ControllerComp extends Component { state = { username:"xiaowang" } valueChange = (event)=>{ console.log(event); this.setState({ username : event.target.value }) } render() { const {username} = this.state return ( <div> <h3>ControllerComp</h3> <p>{username}</p> <input type="text" onChange={this.valueChange} value={username} /> </div> ) } }
总结一下JSX表单元素
<input type="text" readOnly disabled value={username} defaultValue={username} />
readOnly:代表只读,但是可以复制内容
disabled:代表禁用,内容只能访问不能复
value:受控属性
defaultValue:默认的value值,不会被state控制
import React, { Component } from 'react' export default class ControllerComp extends Component { state = { username:"xiaowang", selectorValue:"WEB22", checkValue:false } valueChange = (event)=>{ console.log(event); this.setState({ username : event.target.value }) } typeChange = (event)=>{ this.setState({ selectorValue:event.target.value },()=>{ console.log(this.state); }) } cardChange = (event)=>{ console.log(event.target.value); this.setState({ checkValue:!this.state.checkValue }) } render() { const {username,selectorValue,checkValue} = this.state return ( <div> <h3>ControllerComp</h3> <p>{username}</p> <input type="text" onChange={this.valueChange} value={username} /> {/* <input type="text" readOnly disabled value={username} defaultValue={username} /> */} <select name="" id="" onChange={this.typeChange} value={selectorValue}> <option>WEB21</option> <option>WEB22</option> </select> <input type="checkbox" value="123" onChange={this.cardChange} checked={checkValue} /> </div> ) } }
补充(综合例子):
import React, { Component } from "react"; export default class Work extends Component { state = { formData: { username: "", password: "", education: "本科", gender: "", cardType: [], }, }; changeUsername = (event) => { const { formData } = this.state; formData.username = event.target.value; this.setState({ formData, }); }; changePassword = (event) => { const { formData } = this.state; formData.password = event.target.value; this.setState({ formData, }); }; changeEducation = (event) => { const { formData } = this.state; formData.education = event.target.value; this.setState({ formData, }); }; changeGender = (event) => { const { formData } = this.state; const value = event.target.value; const isChecked = event.target.checked; if (isChecked) { formData.gender = value; this.setState({ formData, }); } }; changeCardType = (event) => { const { formData } = this.state; const value = event.target.value; const isChecked = event.target.checked; if (isChecked) { formData.cardType.push(value); this.setState({ formData, }); } else { const index = formData.cardType.indexOf((item) => item == value); formData.cardType.splice(index, 1); this.setState({ formData, }); } }; register = () => { const { formData } = this.state; console.log(formData); }; render() { const { formData } = this.state; return ( <div> <div> <label htmlFor="">用户名:</label> <input type="text" value={formData.username} onChange={this.changeUsername} /> </div> <div> <label htmlFor="">密码:</label> <input type="password" value={formData.password} onChange={this.changePassword} /> </div> <div> <label htmlFor="">学历:</label> <select name="" id="" value={formData.education} onChange={this.changeEducation} > <option>本科</option> <option>专科</option> <option>高中</option> </select> </div> <div> <label htmlFor="">性别:</label> <label htmlFor="nan">男</label> <input type="radio" id="nan" value="男" checked={formData.gender == "男"} onChange={this.changeGender} /> <label htmlFor="nv">女</label> <input type="radio" id="nv" value="女" checked={formData.gender == "女"} onChange={this.changeGender} /> </div> <div> <label htmlFor="">证件类型:</label> <label htmlFor="sfz">身份证</label> <input type="checkbox" name="cardType" id="sfz" value="身份证" checked={formData.cardType.includes("身份证")} onChange={this.changeCardType} /> <label htmlFor="hz">护照</label> <input type="checkbox" name="cardType" id="hz" value="护照" checked={formData.cardType.includes("护照")} onChange={this.changeCardType} /> </div> <div> <button onClick={this.register}>注册</button> </div> </div> ); } }
二、非受控组件
React中如果要获取组件的数据,我们还可以通过非受控组件
非受控组件:我们在React中可以直接操作DOM,来获取节点,从而得到节点的值
import React, { Component } from 'react' export default class ControllerComp2 extends Component { getInputValue = (event) => { console.log(event.target.value); } getInputValue2 = ()=>{ console.log(this.abc.value); console.log(this.abc2.value); } render() { return ( <div> <h3>ControllerComp2</h3> <input ref={(params)=>this.abc = params} type="text" onBlur={this.getInputValue} /> <input ref={(p)=>this.abc2 = p} type="text" /> <button onClick={this.getInputValue2}>获取文本值</button> </div> ) } }
扩展语法:
import React, { Component } from 'react' export default class ControllerComp2 extends Component { constructor(){ super() this.usernameElement2 = React.createRef() } getInputValue2 = ()=>{ console.log(this.usernameElement2.current); } render() { return ( <div> <h3>ControllerComp2</h3> <input type="text" ref={this.usernameElement2} /> <button onClick={this.getInputValue2}>获取文本值</button> </div> ) } }
官方提供的这种语法,利用React.createRef来创建一个引用,你只需要将这个引用放在节点上面,得到当前这个节点对象
<button onClick={this.getInputValue2}>获取文本值</button>
区别:
-
第一种方案在绑定ref的时候默认要传递一个回调函数。这个回调函数得到原生节点,节点保存起来
-
第二种方案采用React.createRef 创建一个引用,得到的是一个对象,current属性
React08-组件生命周期
React08-组件生命周期
一、组件生命周期阶段
组件生命周期指的就是组件从创建到销毁的整个过程
组件生命周期一共分为三个阶段:
-
挂载阶段:组件的初始化、以及页面渲染节点
-
运行阶段:这个是整个生命周期最长的阶段,主要用于页面更新操作,以及状态管理和重绘
-
卸载阶段:这个阶段组件在销毁的时候,执行的一个生命周期函数
二、流程图
完成流程图:
挂载阶段:
-
执行构造器进行state数据初始化
-
执行componentWillMount,进行挂载之前操作(这个函数已经被废弃了)
-
render方法执行页面渲染
-
执行
componentDidMount
,挂载完毕后执行
运行阶段:
-
判断props是否发生变化。componentWillReceiveProps。这个函数已经废弃。
-
shouldComponentUpdate
可以进行数据更新之前的判断,可以检测props和state数据发生的变化。一定返回一个boolena,true代表执行render更新,false组织我们render调用 -
componentDidUpdate
这个生命周期函数代表更新完毕后的操作销毁阶段:
当组件销毁的时候,我们会执行componentWillUnmount
import React, { Component } from 'react' import Children from './Children' export default class LifeCircle extends Component { constructor() { super(); this.state = { username:"xiaowang", flag:true } console.log("1---构造器正在执行"); } componentWillMount(){ console.log("2---组件挂载之前运行"); } componentDidMount(){ console.log("4---组件挂载完毕"); } changeUsername = ()=>{ this.setState({ username:"xiaofeifei" }) } render() { console.log("3---执行render方法就代表正在执行挂载"); return ( <div> <h3>生命周期</h3> <button onClick={this.changeUsername}>修改username</button> <button onClick={()=>this.setState({flag:false})}>隐藏Children</button> {this.state.flag?<Children username={this.state.username}></Children>:null} </div> ) } }
import React, { Component } from 'react' export default class Children extends Component { // componentWillReceiveProps() { // console.log("props发生变化"); // } state = { password:"123" } shouldComponentUpdate(nextProps,nextState){ console.log("nextProps---",nextProps); console.log("nextState---",nextState); console.log("children的数据正在修改"); return true } componentDidUpdate(){ console.log("更新完毕后的函数"); } componentWillUnmount(){ console.log("组件正在销毁"); } render() { console.log("children的render"); return ( <div> <p>Children</p> <p>{this.state.password}</p> <button onClick={()=>this.setState({password:"000"})}>修改password</button> <p>{this.props.username}</p> </div> ) } }
三、异常处理
异常主要分为两类:
-
请求异常:主要请求401、404、500.
可以在axios响应拦截器里统一处理请求异常。发现401,提示用户身份过期。500服务器异常
-
组件发生异常:
在render方法如果出现异常信息,页面会立即报错。
为了让以后再开发过程中render里抛出的异常信息,我们都要捕获统一处理这个错误。提供了一个生命周期函数来专门处理这个
React16提供了一个内置函数(componentDidCatch),专门用于捕获render里抛出的异常。捕获了这个异常,我们全局处理异常信息。
我们需要在components文件夹下面创建一个公共的错误组件
import React, { Component } from 'react' import Children from './Children' export default class ErrorComp extends Component { constructor() { super() this.state = { hasError:false } } componentDidMount(){ console.log("123"); } componentDidCatch(error,info){ console.log(error); console.log(info); this.setState({ hasError:true }) } render() { if(this.state.hasError){ return <h1>page Error,doSomething</h1> }else{ // this.props.children;代表你们要渲染的页面 // 插槽this.props.children; 代表Children组件 return this.props.children; } } }
提供了一个componentDidCatch函数,这个公共组件用于包裹其他组件,一旦包裹组件发生异常,componentDidCatch就会执行,决定最终渲染的内容
使用这个组件
<ErrorComp> <App></App> </ErrorComp>
this.props.children
react中插槽功能。可以接受到组件中间传递的模板代码。
JS中错误需要自己使用try-catch来捕获
要求:
创建一个父组件、创建一个子组件。
组件一开始加载就页面显示一个到计时。子组件来完成。
父组件可以暂停和开启倒计时。
扩展-三级联动
import React, { Component } from "react"; export default class HomeFrom2 extends Component { state = { province: [ { name: "海南省", city: [ { name: "海口市", area: ["龙华区", "秀英区"], }, { name: "三亚市", area: ["亚龙湾", "三亚湾"], }, ], }, { name: "四川省", city: [ { name: "成都市", area: ["成华区", "高新区"], }, { name: "绵阳", area: ["游仙区", "涪城区"], }, ], }, ], cityArr:[], areaArr:[], }; componentDidMount(){ // 初始化三级联动 const {province} = this.state // 将第一个省的城市赋值给cityArr const citys = province[0].city // 将第一个市的区县赋值给areaArr const areas = citys[0].area this.setState({ cityArr:citys, areaArr:areas }) } // 市修改后,动态修改区县 cityChange = (event) =>{ // 获取市的修改值 const checkCity = event.target.value const {cityArr} =this.state // 通过选中的城市名,查询对应的区县 const areas = cityArr.find(item=>item.name == checkCity).area this.setState({ areaArr:areas }) } // 省修改后,动态修改市和区县 provinceChange = (event) =>{ // 获取省的修改值 const checkProvince = event.target.value const {province} =this.state // 通过选中的省名,查询对应的市 const citys = province.find(item=>item.name == checkProvince).city // 将第一个市的区县赋值给areaArr const areas = citys[0].area this.setState({ cityArr:citys, areaArr:areas }) } render() { const { province,cityArr,areaArr } = this.state; return ( <div className={Styles.container}> <div className={Styles.centerContent}> <div className={Styles.tableContent}> {isShowAdd ? ( <div className={Styles.updateModelContent}> <div className={Styles.centerUpdate}> // start <div className={Styles.formItem}> <label htmlFor="">区域</label> {/* 省 */} <select name="" id="" onChange={this.provinceChange}> { province.map(item=>{ return( <option key={item.name} value={item.name}>{item.name}</option> ) }) } </select> {/* 市 */} <select name="" id="" onChange={this.cityChange}> { cityArr.map(item=>{ return( <option key={item.name} value={item.name}>{item.name}</option> ) }) } </select> {/* 区 */} <select name="" id=""> { areaArr.map(item=>{ return( <option key={item} value={item}>{item}</option> ) }) } </select> </div> <div className={Styles.formItem}> <button onClick={this.added}>添加接种记录</button> </div> </div> </div> ) : ( <></> )} </div> </div> </div> ); } }
面试题01-JS事件循环
面试题01-JS事件循环
一、JavaScript语言特点
JavaScript最大的特点:
-
弱类型语言:定义变量的时候无需指定数据类型,数据类型由值来定
var i = 10; var j = "xiaowang" i = "xiaofeifei" typeof i //主要获取基本数据类型的类型
强类型语言:比如像Java、C++等等。
int i = 10; float a = 20.5
int代表整型、在内存4个字节。
float浮点型,小数类型
数据类型就是决定了当前你的内容在内存里面存放的时候,分配多大的空间。
-
单线程语言:
单线程语法那就意味着同一时间,只能处理一件事儿。
JS这门语法用于前端页面的业务逻辑,最初设计的时候,就已经决定了他是单线程的任务。
比如:我们不可能一边删除dom、一遍修改dom/
在运行代码的时候,代码是从上往下的方式来运行。
上一个任务没有结束,下一个任务不能开始。性能问题
引入了异步的概念。比如请求。文件操作(Nodejs)
二、同步和异步
JavaScript中所有的任务都可以分为同步和异步:
同步任务:立即执行的任务,同步任务一般在主线程中执行,特点,要等待上一个任务完成后继续执行下一个
异步任务:异步任务可以实现异步加载。在执行同步任务的时候,让异步任务等待。请求、setTimout
var i = 20; //console.log(1)//效率也不高 function log(){ var j = 20 console.log(i) } window.log() setTimeout(()=>{ console.log(3) },0) console.log(2) eval("var i = 10;console.log(4)")
执行栈:
上下文,
在JS中主要分为下面几类执行栈:
-
全局执行上下文:运行程序,就会默认产生的,在浏览器中默认window对象,nodejs默认global
-
函数执行上下文:JS中函数每当被调用的时候,就会创建一个上下文对象。
-
eval上下文文:eval是JS提供的一个函数,能够产生一个独立的上下文呢对象。
堆空间
const app = new Student() const app2 = app
三、事件循环
案列:
console.log(1) new Promise((resolve,reject)=>{ console.log(2) resolve("成功的结果") }).then(res=>{ console.log(3) }) setTimeout(()=>{ console.log(4) },0) console.log(5)
1、2、5、3、4
事件循环:(Event Loop)
-
先执行第一个script标签,这个标签是一个宏任务。执行的一个任务就是宏任务
-
代码运行的时候,创建对应执行栈。遇到同步任务就从上往下运行
-
遇到异步任务就分析这个任务是属于宏任务还是微任务。宏任务存放宏任务队列,微任务就存放微任务队列
-
主线程继续执行代码。
-
主线程代码执行完毕后,会去任务队列中执行回调函数。
-
优先会执行微任务队列。
-
在执行宏任务队列。
-
直到所有的任务都执行完毕后,我们就结束程序
<script> console.log(1) new Promise((resolve,reject)=>{ console.log(2) resolve("成功的结果") }).then(res=>{ console.log(3) }) setTimeout(()=>{ console.log(4) },0) console.log(5) </script>
script 这个标签也有特殊含义。他是一个宏任务。
程序从一开始发现有script,优先执行第一个宏任务。
<script> console.log(1) new Promise((resolve,reject)=>{ console.log(2) resolve("成功的结果") }).then(res=>{ console.log(3) }) setTimeout(()=>{ console.log(4) },0) console.log(5) </script> <script> </script>
宏任务:script标签本身就是一个宏任务、setTimeout、setInterval UI rendering、Nodejs IO
微任务:Promise.then、XMLHttpRequest(ajax)、nextTick()
四、实战
async function async1() { console.log('async1 start') await async2() console.log('async1 end') //进入微任务队列 } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('settimeout') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end')
结果为:
script start—>async1 start—>async2—>promise1—>script end—>async1 end—>promise2—>settimeout
2==[[2]]
React09-全家桶项目搭建
React09-全家桶项目搭建
一、路由环境的搭建
(1) 下载路由对应依赖包
yarn add react-router-dom@5
(2)搭建路由页面
一级路由
Login.jsx\Register.jsx\Home.jsx\ForgetPassword.jsx
二级路由
再pages下面创建subs文件夹,里面存放的就是二级路由
main/主页页面。默认进入后访问的页面。
user/用户界面。针对系统能够登录的用户的进行增删改查
role/角色模块,角色和访问权限有关联,不同角色,对应不同权限
shop/店铺管理,将店铺的名字、地图、推广图显示出来
product/商品模块,商品列表、商品分类信息(家用电器-电饭锅-各种电饭锅)
charts/ 图标统计、后续会做成数据可视化的图表
二、UI组件库搭建
antd环境搭建
antd是蚂蚁金服开源的一个优秀的UI组件库。
对于前端工程师来说我们直接使用里面UI组件。
antd for vue(面向vue3):https://www.antdv.com/docs/vue/getting-started-cn/
antd for React:Ant Design of React - Ant Design
(1)下载antd
yarn add antd
(2)在index.js引入antd的全局css样式
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import 'antd/dist/antd.css'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> );
(3) 使用antd组件
你们可以在任何一个页面中引入antd
import {Button} from "antd" <Button></Button>
antd主题色配置
(1)我们需要下载第三方的插件,来更改我们项目的启动命令
yarn add @craco/craco
(2)下载完后,我们需要更改项目的启动命令
打开package.json文件,将默认的启动命令替换为下面的代码
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" },
(3) 下载less插件加载到项目中
antd默认底层采用less来设计样式,
设计了一个变量,来代表主题色.这个变量less来定义
安装好less环境,自己写一个颜色来覆盖他底层的默认颜色
有时下载其他依赖包,运行时会出现craco报错,再运行以下代码即可:
yarn add craco-less
我们需要在index.js 中引入less文件
import 'antd/dist/antd.css'; // 删除 import 'antd/dist/antd.less';
(4)配置主题色
在项目的根目录下面(不是src里面)创建一个js文件.名字craco.config.js
const CracoLessPlugin = require('craco-less'); module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { '@primary-color': '#1DA57A' }, javascriptEnabled: true, }, }, }, }, ], };
antd打包优化:按需打包
目前我们在index.js中引入了antd的less文件
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import 'antd/dist/antd.less'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> );
最后项目打包的时候,不管你是否用到某个antd组件,将所有的css样式都打包到最终build代码中
想要实现的业务;你项目中用到了哪些组件,我们对应打包他的样式.没有用到组件,css样式就不打包
antd组件默认按需加载\antd样式按需加载?
(1)将index.js中antd全局样式删除
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; // import 'antd/dist/antd.less'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> );
(2) 下载插件来配置按需打包
npm install babel-plugin-import yarn add babel-plugin-import
babel-plugin-import
这个插件可以实现webpack按需加载
(3)修改craco.config.js
配置文件
const CracoLessPlugin = require('craco-less'); module.exports = { babel: { plugins: [ ["@babel/plugin-proposal-decorators", { legacy: true }], //装饰器 [ "import", { "libraryName": "antd", "libraryDirectory": "es", "style": true } ] ] }, plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { '@primary-color': '#bae637' }, javascriptEnabled: true, }, }, }, }, ], };
三、函数组件开发
目前我们在React中有两种组件可以使用
-
类组件开发
优点
基于面向对象开发思想,继承\重写等等概念.
类组件开发提供了完善的功能,包括组件内部状态state\组件外部props\完整生命周期函数
缺点:
具备面向对象的基础知识点.
类组件开发,对象之间的关联关系,强关联.
this指向问题,是我们开发必须要考虑的问题
-
函数组件开发
优点:
基于函数式编程,写法更加简单.只需要在一个组件中定义一个函数,并暴露这个函数
函数组件开发模式相对类来说,更加简单.没有this指向问题
缺点:
函数组件是无状态组件的组件,函数组件没有提供生命周期函数
目前在公司里面,大部分业务都会采用函数组件来开发.混着类组件一起使用
创建函数组件模板
rfc快捷键就可以开始搭建我们函数组件的代码模板
从React16.8这个版本后,官方给我们提供了很多hook函数,来辅助我们函数组件开发
四、搭建页面环境
表单验证规则
<Form.Item name="username" rules={[ { required: true, message: '用户名不能为空', }, { pattern:/^[a-zA-Z0-9]{3,}$/, message:"长度必须3位以及以上" } ]} >
pattern代表自定义正在验证.后面自己设计一个正则表达式
fileUpload(文件上传)组件二次封装
import { PlusOutlined } from "@ant-design/icons"; import { Modal, Upload } from "antd"; import React, { useState } from "react"; const getBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); }); const FileUpload = (props) => { // 定义预览数据 const [previewVisible, setPreviewVisible] = useState(false); const [previewImage, setPreviewImage] = useState(""); const [previewTitle, setPreviewTitle] = useState(""); // 上传列表 const [fileList, setFileList] = useState([]); // 取消预览 const handleCancel = () => setPreviewVisible(false); // 图片回显 const handlePreview = async (file) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj); } setPreviewImage(file.url || file.preview); setPreviewVisible(true); setPreviewTitle( file.name || file.url.substring(file.url.lastIndexOf("/") + 1) ); }; // 上传文件 const handleChange = ({ file, fileList: newFileList }) => { // 当上传文件状态为done时,将数据传给父组件 if (file.status == "done") { props.fetchFileData(file.response.data); } setFileList(newFileList); }; const uploadButton = ( <div> <PlusOutlined /> <div style={{ marginTop: 8, }} > Upload </div> </div> ); return ( <> <Upload // 上传路径 action="http://127.0.0.1:8002/goods/fileUpload" listType="picture-card" fileList={fileList} onPreview={handlePreview} onChange={handleChange} // 设置上传后端的name,必填!根据接口文档填写 name="imgSrc" > {/* 设置最大显示个数 */} {fileList.length >= 2 ? null : uploadButton} </Upload> <Modal visible={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel} > <img alt="example" style={{ width: "100%", }} src={previewImage} /> </Modal> </> ); }; export default FileUpload;
Table组件分页封装
<Table columns={columns} dataSource={data} bordered // 每一条的key值 rowKey="_id" //分页代码 pagination={{ // 默认当前页 defaultCurrent: 2, // 默认页面显示条数 defaultPageSize: 8, // 显示条数选项 pageSizeOptions: [8, 10, 12], // 是否显示页面快速跳转 showQuickJumper: true, // 是否显示条数下拉框 showSizeChanger: true, }} />
树形组件基本使用
// 封装的属性组件 import { Modal, Tree } from "antd"; import React from "react"; // 定义树形组件数据 const treeData = [ { title: "首页", key: "/home/main", }, { title: "用户", key: "/home/user", }, { title: "角色", key: "/home/role", }, { title: "店铺", key: "/home/shop", }, { title: "商品", key: "/home/product", children: [ { title: "商品分类", key: "/home/product/list", }, { title: "商品列表", key: "/home/product/category", }, ], }, { title: "信息统计", key: "/home/charts", children: [ { title: "工资流水", key: "/home/charts/salary", }, { title: "销售管理", key: "/home/charts/sale", }, ], }, ]; export default function Auth(props) { // 模态框方法 const handleOk = () => { props.closeModel(); }; const handleCancel = () => { props.closeModel(); }; // 树形组件方法 // 多选框change时的方法 const onCheck = (checkedKeysValue) => { // 输出的是选中的数组 console.log("onCheck", checkedKeysValue); }; const onSelect = (selectedKeysValue, info) => { console.log("onSelect", info); }; return ( <Modal title="授权" visible={props.isModalVisible} onOk={handleOk} onCancel={handleCancel} > <Tree checkable // 多选框change时调用 onCheck={onCheck} onSelect={onSelect} // 树形控件的基本数据 treeData={treeData} // 默认选中的值 defaultCheckedKeys={props.row.menus} // 默认展开的选项 defaultExpandedKeys={["/home/product", "/home/charts"]} /> </Modal> ); }
级联组件基本使用
import React, { useState, useEffect } from "react"; import { Cascader} from "antd"; import { findAllCategroyApi } from "../../../apis/categoryApis"; export default function AddProduct(props) { const [options, setOptions] = useState([]); const [goodType, setGoodType] = useState(""); // 商品分类联动下拉框数据 useEffect(() => { fetchGoodsType(); }, []); const fetchGoodsType = async () => { const res = await findAllCategroyApi({ parentId: 0 }); setOptions(res.data); }; // ***********级联下拉框方法(value是选中的数组,***value2***可以得到更多的信息)*********** const onChange = (value, value2) => { setGoodType(value2[1].id || ""); }; return ( <> <Form {...layout} name="nest-messages" onFinish={onFinish} validateMessages={validateMessages} > <Form.Item name={["type"]} label="商品的分类" rules={[ { required: true, }, ]} > <Cascader options={options} onChange={onChange} placeholder="请选择商品分类" value /> </Form.Item> <Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}> <Button type="primary" htmlType="submit"> 添加 </Button> </Form.Item> </Form> </Card> </> ); }
根据用户角色,渲染不同的菜单
src/config/menuConfig.js中
import { AppstoreOutlined, MailOutlined, SettingOutlined, } from "@ant-design/icons"; // 定义菜单数据 const menuData = [ { title: "首页", key: "/home/main", icon: <AppstoreOutlined />, }, { title: "用户", key: "/home/user", icon: <MailOutlined />, }, { title: "角色", key: "/home/role", icon: <SettingOutlined />, }, { title: "店铺", key: "/home/shop", icon: <AppstoreOutlined />, }, { title: "商品", key: "/home/product", icon: <AppstoreOutlined />, children: [ { title: "商品分类", key: "/home/product/list", icon: <AppstoreOutlined />, }, { title: "商品列表", key: "/home/product/category", icon: <AppstoreOutlined />, }, ], }, { title: "信息统计", key: "/home/charts", icon: <AppstoreOutlined />, children: [ { title: "工资流水", key: "/home/charts/salary", icon: <AppstoreOutlined />, }, { title: "销售管理", key: "/home/charts/sale", icon: <AppstoreOutlined />, }, ], }, ]; export default menuData;
组件src/components/home/MyMenu中
import React, { useState, useEffect } from "react"; import { Menu } from "antd"; import { Link } from "react-router-dom"; import menuData from "../../config/menuConfig"; export default function MyMenu() { // 存储用户存在的path数组 例如:['/home/main', '/home/product', '/home/product/list', '/home/product/category'] const [userMenus, setUserMenus] = useState([]); useEffect(() => { // 从用户信息中获取用户的menus(path数组 ) const { menus } = JSON.parse(localStorage.userInfo).role; setUserMenus(menus); }, []); // 初始化菜单 const initMenu = (list) => { return list.map((item) => { // 如果用户的菜单数组包含了菜单数组的item.key则渲染(不同身份的用户,看到的菜单不同) if (userMenus.includes(item.key)) { // 如果没有二级菜单则进入if if (!item.children) { return ( <Menu.Item key={item.key} icon={item.icon}> <Link to={item.key}>{item.title}</Link> </Menu.Item> ); // 有二级菜单则进入else } else { return ( <Menu.SubMenu key={item.key} icon={item.icon} title={item.title}> {/* 递归 */} {initMenu(item.children)} </Menu.SubMenu> ); } } }); }; return ( <Menu theme="dark" defaultOpenKeys={["sub1", "sub2"]} mode="inline"> {initMenu(menuData)} </Menu> ); }
五、搭建网络请求
在React中我们也选中使用axios来进行网络请求处理
axios=ajax+promise
ajax技术(页面局部更新)基本上称为标准,任何一个浏览器都支持ajax
jquery ajax != ajax
jquery的ajax和axios都是市面上封装过后工具
$.ajax({ method:"get", url:"" })
ajax的代码流程
//(1)创建ajax的核心对象 const xmlhttp = new XMLHttpRequest() // (2)和服务器进行连接 xmlhttp.open("http://127.0.0.1:8001/user","get") xmlhttp.send("id=1") // (3)获取响应结果.一直在监控状态码变化 xmlhttp.onreadystatechange = function(resp){ //状态码等于200,readyState=4代表后端已经将数据传输完毕,前端可以获取 if(xmlhttp.state==200 && xmlhttp.readyState==4){ const res = resp.reponseText } }
jquery的代码
function ajax({url,method="GET",data,success}){ //(1)创建ajax的核心对象 const xmlhttp = new XMLHttpRequest() // (2)和服务器进行连接 xmlhttp.open(url,method) xmlhttp.send(data) // (3)获取响应结果.一直在监控状态码变化 xmlhttp.onreadystatechange = function(resp){ //状态码等于200,readyState=4代表后端已经将数据传输完毕,前端可以获取 if(xmlhttp.state==200 && xmlhttp.readyState==4){ const res = resp.reponseText success(res) } } } 使用自己的ajax ajax({ url:"", method:"GET", data:"id=1", success(result){ } })
promise技术
const promise = new Promise((reslove,reject)=>{ ajax({ url:"", method:"GET", data:"", success(res){ reslove(res) }, fail(error){ reject(error) } }) }) promise.then(res=>{ })
axios这种请求封装工具
axios.get() axios.post() axios({ url:"", method:"" }) await axios.get() axios.get("/url").then(res=>{ })
ECMAScript2015
ECMAScript2021
下载axios对应依赖包
yarn add axios //craco-less 不存在(可能出现less报错,重新下载craco-less即可) yarn add craco-less
utils文件夹里创建axiosUtils.js
import axios from "axios" import {message} from "antd" const newIntances = axios.create({ baseURL:"http://127.0.0.1:8002", timeout:3000 }) // 请求拦截器 newIntances.interceptors.request.use((req)=>{ // 给请求头设置token let token = localStorage.token if(token){ req.headers.token = token } return req },(error)=>{ // 直接页面抛出一个错误 Promise.reject(error) }) newIntances.interceptors.response.use((resp)=>{ return resp.data },(error)=>{ const response = error.response if(response){ switch(response.status){ case 500: message.error("后端服务器异常!") break; case 401: message.error("身份过期,请重新登录") localStorage.removeItem("token") localStorage.removeItem("userInfo") window.location.href="/login" break; case 404: message.error("请求路径失败") break; } } return Promise.reject("自定义错误") }) export default newIntances
在api文件夹下面创建categoryApi.js
import axios from "../utils/axiosUtils" // 获取到所有的分类信息 export const getAllCategory = (data)=>axios.get("/categroy/findCategroy",{params:data}) // 添加分类信息 // 删除分类信息
页面中使用
import React, { useState,useEffect } from 'react' import { Card, Button, Table } from 'antd'; import {getAllCategory} from "../../../apis/categoryApi" export default function Category() { const [data,setData] = useState([]) // componentDidMount useEffect(()=>{ fecthData() },[]) const fecthData =async ()=>{ const res = await getAllCategory({parentId:0}) console.log(res); setData(res.data.data) } return ( <Card title="商品分类信息" extra={<Button type='default'>添加</Button>} style={{ width: "100%", }} > <Table columns={columns} dataSource={data} bordered /> </Card> ) }
六、高阶组件处理路由权限
在src/components/RouterAuth.jsx中
import React, { Component } from "react"; import RouterConfig from "../config/routerConfig"; import { Redirect, Route } from "react-router-dom"; export default class RouterAuth extends Component { render() { //(1)默认当前获取到访问路由地址 const { pathname } = this.props.location; //(2)判断token是否存在 const token = localStorage.token; // (3)你访问路由地址是否在我们映射文件中 const targetRouterConfig = RouterConfig.find((item) => { // 去除所有空格之后,item.path和pathname相等则返回 return item.path.replace(/\s+/g, "") === pathname; }); // (4)判断你访问地址在配置文件中,进入下一步 if (targetRouterConfig) { if (targetRouterConfig.auth) { if (token) { return ( <Route path={targetRouterConfig.path} component={targetRouterConfig.component} ></Route> ); } else { return <Redirect to="/login"></Redirect>; } } else { return ( <Route path={targetRouterConfig.path} component={targetRouterConfig.component} ></Route> ); } } else { return <Redirect to="/404"></Redirect>; } } }
在src/config/routerConfig.js中
import Home from "../pages/Home"; import Login from "../pages/Login"; import Register from "../pages/Register"; import NotFind from "../pages/NotFind"; // 用于存路由相关的数据,(以后该数据从后端来) let routers = [ { path: "/login", name: "Login", component: Login }, { path: "/register", name: "Register", component: Register }, { path: "/404", name: "NotFind", component: NotFind }, { path: "/home", name: "Home", component: Home, auth: true }, { path: "/home/main", name: "Main", component: Home, auth: true }, //这里必须是一级路由对应的组件 { path: "/home/user", name: "User", component: Home, auth: true }, { path: "/home/role", name: "Role", component: Home, auth: true }, { path: "/home/shop", name: "Shop", component: Home, auth: true }, { path: "/home/charts/salary", name: "Salary", component: Home, auth: true }, { path: "/home/charts/sale", name: "Sale", component: Home, auth: true }, { path: "/home/product/list", name: "List", component: Home, auth: true }, { path: "/home/product/category", name: "Category", component: Home, auth: true, }, ]; export default routers;
routes里面没有配置的路径,就代表用户没有权限访问。auth属性代表必须token登录后才能加载的组件
在App.jsx中引用 <RouterAuth></RouterAuth>高阶组件
import React, { Component } from "react"; import { BrowserRouter, Switch, Route } from "react-router-dom"; // import Home from './pages/Home' // import Login from './pages/Login' // import Register from './pages/Register' // import ForgetPassword from './pages/ForgetPassword' // import Todolist from './pages/Todolist' import RouterAuth from "./components/RouterAuth"; export default class App extends Component { render() { return ( <div> <React.Suspense fallback={<div>一级路由加载中...</div>}> <BrowserRouter> <Switch> {/* 跟Vue的路由渲染出口时一个意思。采用高阶组件 */} <RouterAuth></RouterAuth> {/* <Redirect exact from='/' to="/home"></Redirect> */} {/* <Route exact path="/" component={Home}></Route> <Route path="/home" component={Home}></Route> <Route path="/login" component={Login}></Route> <Route path="/register" component={React.lazy(()=>import("./pages/Register"))}></Route> <Route path="/forgetPassword" component={ForgetPassword}></Route> <Route path="/todoList" component={Todolist}></Route> */} </Switch> </BrowserRouter> </React.Suspense> </div> ); } }
路由渲染出口由RouterAuth来确定。
七、echarts图表统计
(1)下载依赖包
yarn add echarts-for-react yarn add echarts
(2)在项目中引入echarts-for-react
import ReactEcharts from "echarts-for-react"
(3)在项目中引入组件并设置图表类型
import React from 'react' import ReactEcharts from "echarts-for-react" export default function Salary() { const getOption = () => { return { xAxis: {}, yAxis: {}, series: [ { symbolSize: 20, data: [ [10.0, 8.04], [8.07, 6.95], [13.0, 7.58], [9.05, 8.81], [11.0, 8.33], [14.0, 7.66], [13.4, 6.81], [10.0, 6.33], [14.0, 8.96], [12.5, 6.82], [9.15, 7.2], [11.5, 7.2], [3.03, 4.23], [12.2, 7.83], [2.02, 4.47], [1.05, 3.33], [4.05, 4.96], [6.03, 7.24], [12.0, 6.26], [12.0, 8.84], [7.08, 5.82], [5.02, 5.68] ], type: 'scatter' } ] } } return ( <div> <ReactEcharts option={getOption()} style={{ height: "350px", width: "50%" }} className="react_for_echarts" ></ReactEcharts> </div> ) }
通过发异步请求获取数据,然后渲染到echart中
import React,{useState,useEffect} from 'react' import ReactEcharts from "echarts-for-react" import {findAllCategroyApi} from "../../../apis/categoryApis" export default function Salary() { const [categoryData,setCategoryData] = useState([]) const [categoryXData,setCategoryXData] = useState([]) const [categoryYData,setCategoryYData] = useState([]) useEffect(()=>{ fetchCategoryData() },[]) const fetchCategoryData = async() =>{ const res = await findAllCategroyApi({parentId:0}) setCategoryData(res.data) } useEffect(()=>{ console.log(categoryData); let arrX = [] let arrY = [] categoryData.forEach(item=>{ arrX.push(item.value) arrY.push(item.children.length) }) setCategoryXData(arrX) setCategoryYData(arrY) },[categoryData]) const chartData = () =>{ return ( { xAxis: { type: 'category', data: categoryXData }, yAxis: { type: 'value' }, series: [ { data: categoryYData, type: 'bar' } ] } ) } return ( <div> <ReactEcharts option={chartData()} style={{width:"350px",height:"300px"}} className={"react_for_echarts"} ></ReactEcharts> </div> ) }
八、搭建状态机(最后)
React10-路由环境搭建
React10-路由环境搭建
一、概念
React这个库官方并没有提供路由。推荐我们使用一个第三方的路由库。React-Router
使用React-Router这个库里面提供的组件来完成路由搭建。
访问路由库的文档:https://reactrouter.com/web/api/Redirect
React-Router路由模式:
我们采用的是编程式路由,就需要你们自己在组件中引入路由模块,自己搭建对应路由映射。
Vue这个框架配置式路由,你只需要在配置文件中,把映射关系设置,自动生成路由代码
const routes = [ { path:"/", redirect:"/home" } { path:"/home", component:Home } ] new VueRouter({ routes })
二、搭建路由环境
(1) 下载依赖
yarn add react-router-dom@5
(2)需要在项目中引入路由组件
import {Route,Link,HashRouter} from "react-router-dom"
react-router-dom:提供了分成多的组件,利用这些组件我们可以搭建路由配置
我们需要在App.js里面搭建路由映射
import React, { Component } from 'react' import { HashRouter, Switch, Route } from "react-router-dom" import Login from './pages/login/Login' import Register from './pages/register/Register' import Home from './pages/home/Home' /** * 搭建一级路由 * 以后访问App.js 这个组件的时候,根据你们访问地址来决定在Appjs显示不同页面 */ export default class App extends Component { render() { return ( <div> {/* 搭建路由映射关系 */} <HashRouter> <Switch> <Route path="/login" component={Login}></Route> <Route path="/register" component={Register}></Route> <Route path="/home" component={Home}></Route> </Switch> </HashRouter> </div> ) } }
三、解析路由配置
路由配置中的一些组件
-
路由器组件
<HashRouter> :默认采用的window.location.hash来实现路由的映射。默认会在地址栏URL里添加#来代表hash映射
<BrowserRouter> :默认采用HTML5 history api来实现的路由,地址栏里面不会有#
配置路由映射,必须先指定路由器。而且,整个代码有且仅有一个路由器。
-
路由匹配器
<Swicth> :类似于你们编程中switch语句,默认代表每次匹配一个路由就返回
<Route> :代表路由映射组件,你需要将url地址和代码中组件建立映射关系
-
导航组件
<Link> :Link组件相当于Vue中router-link
<NavLink> :页可以进行页面导航跳转,可以动态添加class样式
<Redirect> :可以重定向到指定的路由
四、路由匹配细节
Switch下面Route在进行匹配的时候默认是模糊匹配
<BrowserRouter> <Switch> <Redirect exact from='/' to="/home"></Redirect> {/* <Route exact path="/" component={Login}></Route> */} <Route path="/login" component={Login}></Route> <Route path="/register" component={Register}></Route> <Route path="/home" component={Home}></Route> </Switch> </BrowserRouter>
exact:可以设置Redirect组件,也可以设置Route组件上。精确匹配
Link组件和NavLink使用
import React, { Component } from 'react' import {Link,NavLink} from "react-router-dom" export default class Login extends Component { render() { return ( <div> <h3>Login</h3> <Link to="/home">去到主页</Link> <NavLink to="/home">NavLink去到主页</NavLink> </div> ) } }
所有的路由组件,都必须在路由器的管辖范围
React11-路由跳转和参数传递
React11-路由跳转和参数传递
一、路由跳转
在React中我们使用ReactRouter这个第三方路由插件来完成路由搭建。
有两种方式:
-
采用超链接的方式来跳转
<Link to="/home">文本</Link> <NavLink to="/home">文本</NavLink>
-
采用按钮的形式来进行跳转
使用按钮的形式来进行跳转,我们需要使用JS代码来进行路由跳转
this.props.history.replace("/home")
当你的页面是采用路由的形式来进行加载的。
路由页面props对象身上会默认新增三个对象
history:代表历史对象,里面包含页面跳转所有方法。这个对象就是H5中history
location:包含的路由的路径和路由传递的数据
match:也包含路由的路径和传递的数据
history对象跳转的时候特点:
history.push()
跳转到指定的页面,会more你保存当前历史记录,可以在浏览器返回
history.replace()
跳转过后不会记录历史,无法返回到之前页面
history.go()
可以指定返回到指定的层级
二、非路由组件
我们在进行路由跳转的时候,路由映射组件默认props身上会出现路由跳转对象,路由参数传递对象。
但是如果一个非路由组件,你们直接import导入到页面中组件。props身上并没有history、location对象
ReactRouter给我们提供了一个高阶组件,可以解决这个问题
import React, { Component } from 'react' import {withRouter} from "react-router-dom" class SelectComp extends Component { typeChange = (event) => { const value = event.target.value if(value=="a"){ console.log(this.props); this.props.history.replace("/login") <select name="" id="" onChange={this.typeChange}> <option value="">请选择</option> <option value="a">注销</option> <option value="b">修改密码</option> </select> </div> ) } } //const newComp = withRouter(SelectComp) export default withRouter(SelectComp)
高阶组件时React中提出的一个概念。还有一个名字HOC
高阶组件本质上就是一个高阶函数(JS概念)
高阶函数:英文叫Higher-order function。JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
数组里对数据进行遍历的方法很多都是高阶函数
const withRouter = (Component)=>{ //默认获取路由对象 const history = {} const location = {} const match = {} //判断Component身上是否已经存在,不存在 return <Component history={history} location={location} match={match} > } const newComponent = withRouter ()
面试题:
-
请说一下React中HOC?
HOC代表的React中高阶组件,高阶组件本质上就是一种高阶函数。我们引入一个高阶组件,接受一个组件,对当前这个组件进行动态功能叠加,最后返回新的组件。这个就是高阶组件。
在React我们用到官方高阶组件withRoter
三、路由参数传递
Link进行跳转
当我们使用Link标签来进行跳转的时候
<Link to={{pathname:"/register",query:{id:1}}}>没有注册?去注册</Link>
在另外一个组件中接受传递过来 值
this.props.location.query
按钮跳转的时候
gotoRegister = ()=>{x this.props.history.push({ pathname:"/register", query:{ id:2 } }) }
在另外一个页面中获取
this.props.location.query
拼接参数到地址栏
<Link to="/register?name=xiaowang">没有注册?去注册</Link> gotoRegister = ()=>{x this.props.history.push("/register?name=xiaofeifei") }
获取参数
this.props.location.search===> ?name=xiaowang
采用动态路由来传递参数
参数的路径上面,作为路由访问一部分
<Route path="/home/:name" component={Home}></Route>
传递参数的时候
this.props.history.replace("/home/xiaowang")
获取参数
this.props.match.params ==> {name:"xiaowang"} <Route path="/home/:name?" component={Home}></Route>
你可以在路由路径上面动态参数部分添加?代表这个动态部分可以不传递
React12-路由懒加载
React12-路由懒加载
一、路由懒加载
路由懒加载也称为路由延迟加载
当你在App.js中引入你页面,在启动项目加载的时候,引入页面就被被加载
增加启动时间,对内存进行消耗。
我们针对不常用的路由组件进行懒加载(延迟加载)
二、官方推荐的方案
以前我们还可以使用第三方的路由懒加载工具 Loadable
开发步骤:
需要在路由器外面增加一个React.Suspence
import React, { Component } from 'react' import { HashRouter, BrowserRouter, Switch, Route, Redirect, Link } from "react-router-dom" import Login from './pages/login/Login' // import Register from './pages/register/Register' import Home from './pages/home/Home' import Loading from './components/Loading' /** * 搭建一级路由 * 以后访问App.js 这个组件的时候,根据你们访问地址来决定在Appjs显示不同页面 */ export default class App extends Component { render() { return ( <div> {/* 搭建路由映射关系 */} <React.Suspense fallback={<Loading></Loading>}> <BrowserRouter> <div> <Switch> <Redirect exact from='/' to="/home"></Redirect> <Route path="/login" component={Login}></Route> <Route path="/register" component={React.lazy(()=>import("./pages/register/Register"))}></Route> <Route path="/home" component={Home}></Route> </Switch> </div> </BrowserRouter> </React.Suspense> </div> ) } }
fallback: 代表回调,当你页面加载很慢的情况,我们会使用fallback动画加载
React.lazy() 可以在你访问这个路由的时候,才动态加载你们的组件。
// import Register from './pages/register/Register'
二级路由懒加载
我们需要在二级路由映射关系外面再增加一个加载动画
<React.Suspense fallback={<div>二级路由懒加载。。。</div>}> <Switch> <Redirect exact from='/home' to="/home/user"></Redirect> <Route path="/home/user" component={User}></Route> <Route path="/home/product" component={React.lazy(()=>import("../subs/product/Product"))}></Route> <Route path="/home/dolist" component={TodoList}></Route> </Switch> </React.Suspense>
二级路由懒加载,用的里面的动画
React13-hook编程
React13-hook编程
一.入门概念
函数组件目前在企业开发中使用非常多,但是函数组件没有提供完整生命周期\内部状态等等
我们在React16.8过后推出一些api函数,这些api函数称为hook.
在hook没有出现之前我们开发一直都是类组件编程.函数组件只负责一些基础页面布局
二.常用hook函数
useState函数
用于定义函数组件内部状态.
你引入这个hook后将数据定义到这个函数中,页面能够实现响应式数据变化
import React, { useState } from 'react' export default function Hook() { // 只要你定义组件内部的数据,都需要解构一个变量,和一个函数 const [username,setUsername] = useState("xiaowang") const [password,setPassword] = useState("password") const [user,setUser] = useState({id:1,name:"xiaowang"}) const changeUsername = ()=>{ setUsername("xiaofeifei") setPassword("123456") } const changeUser = ()=>{ setUser({...user,name:"xiaoliu"}) } const changePwd = ()=>{ setPassword(()=>{ // 执行一段代码,返回修改数据 return "0000" }) } return ( <div> <p>{username}</p> <p>{password}</p> <p>{user.id}</p> <p>{user.name}</p> <button onClick={changeUsername}>修改</button> <button onClick={changeUser}>修改user</button> <button onClick={changePwd}>修改password</button> </div> ) }
useState是属于React官方提供的hook函数.我们引入的时候
import {useState} from "react"
我们在定义useState参数的时候
const [username,setUsername] = useState("xiaowang")
修改username的函数,命名必须按照set+变量名字—小驼峰的方式
修改我们useState的数据,你可以调用修改函数来修改,可以传递一个回调函数
setPassword(()=>{ // 执行一段代码,返回修改数据 return "0000" })
修改数据的函数是同步函数异步?异步
目前暂时无法获取修改后的值,需要借助于其他的hook来解决
可以用useEffect来监控被修改的数据,在useEffect中来获取这个值。
useMemo\useCallback函数
这个函数是我们hook编程里面提供的一个计算属性的api
你可以在函数组件中使用他来进行计算属性
import React, { useState,useMemo,useCallback } from 'react' export default function Hook() { // 只要你定义组件内部的数据,都需要解构一个变量,和一个函数 const [username,setUsername] = useState("xiaowang") const [password,setPassword] = useState("password") const [user,setUser] = useState({id:1,name:"xiaowang"}) const fullName = useMemo(()=>{ return username + password },[username,password]) const newUser = useMemo(()=>{ return user.name + "-----" },[user]) const newUser2 = useCallback(()=>{ return user.name + "-----" },[user]) const changeUsername = ()=>{ setUsername("xiaofeifei") setPassword("123456") } const changeUser = ()=>{ setUser({...user,name:"xiaoliu"}) } const changePwd = ()=>{ setPassword(()=>{ // 执行一段代码,返回修改数据 return "0000" }) } return ( <div> <p>{fullName}</p> <p>{newUser}</p> <p>{newUser2()}</p> <button onClick={changeUser}>修改user</button> <button onClick={changePwd}>修改password</button> </div> ) }
在进行计算属性的时候,[监控的属性],一旦属性值发生变化,页面就会立即更新
useMemo和useCallback区别:
useMemo返回的计算属性的结果,是一个变量
useCallback返回的函数,我们需要调用一下这个函数,当你的变量没有发生变化的时候,返回的永远都是缓存的函数
useEffect函数
在函数组件中默认没有生命周期函数,我们可以执行useEffect完成副作用.
useEffect来模拟生命周期.
主要你的代码中使用useEffect,组件产生副作用.
模拟componentDidMount过程
import React, { useEffect,useState } from 'react' export default function Hook2() { // 这个代码就模拟了componentDidMount const [username,setUsername] = useState("xiaowang") useEffect(()=>{ console.log("componentDidMount"); },[]) const changeUsername = ()=>{ setUsername("setUsername") } return ( <div> <h3>Hook2</h3> <p>{username}</p> <button onClick={changeUsername}>修改username</button> </div> ) }
模拟componentDidUpdate
useEffect(()=>{ console.log("------"); })
第二个参数不传递的时候,页面上任何的数据发生变化,我们都需要执行回调函数,传参时,就监听该参数。
useEffect(()=>{ console.log("componentDidUpdate"); console.log(username); },[username]) useEffect(()=>{ console.log("componentDidUpdate"); console.log(username); },[username,password])
模拟componentWillUnmount销毁
import React, { useEffect } from 'react' export default function MyDemo({ username, callback }) { useEffect(() => { console.log("mydemo---componentDidMount"); return ()=>{ console.log("mydemo---componentWillUnmount"); } },[]) return ( <div> <h3>MyDemo</h3> <p>{username}</p> <button onClick={callback}>传递参数</button> </div> ) }
useHistory函数
可以用于页面跳转(非官方的方法)
//先引入useHistory import {useHistory} from "react-router-dom" export default function Hook2(props) { // 定义history const history = useHistory(); // 点击跳转添加页面 const toAddProduct = () => { //方法一 // props.history.push("/home/product/addProduct"); // 方法二 history.push("/home/product/addProduct") }; return ( <div> <button onClick={toAddProduct}>页面跳转</button> </div> ) }
模拟componentDidUpdate
React14-hook扩展
React14-hook扩展
一、第三方hook
路由提供的常用hook函数
import React,{useRef} from 'react' import {useHistory} from "react-router-dom" export default function Sale() { const history = useHistory() const getValue = () => { history.push("/home/shop") } return ( <div> <button onClick={getValue}>获取文本框值</button> </div> ) }
useHistory我们可以进行页面跳转。属于路由组件提供的hook,称为第三方hook
接下来是useLocation这个hook
import React,{useEffect} from 'react' import {useLocation} from "react-router-dom" export default function Shop() { const location = useLocation() useEffect(()=>{ console.log(location.query); },[location]) return ( <div> <p>location:</p> </div> ) }
useParams:这个hook专门用于获取到我们页面传递的参数。通过动态路由来进行传递
<Route path="/home/shop/:name"> history.push("/home/shop/xiaowang") const params = useParams() ===>{name:"xiaowang"}
学习状态机的时候,也会接收到第三方的hook
二、自定义hook
在我们函数组件中我们需要配合hook来进行使用,而且用了hook过后发现非常方便。
按照每个组件业务来决定我需要引入哪些hook
我们可以将自己代码中某些业务提取出去封装了常见的hook函数,以后你们可以在任何一个组件中使用自己封装的hook,将这个过程称为自定义hook
自定义组件:将页面公共的部分提取出去
自定hook:将业务的逻辑部分提取出去
自定义hook要求:
-
自定义hook的作用是提取公共逻辑,所以一般不会返回JSX对象,根据需要返回对应的结果
-
自定义hook要求名字必须是use开头的函数,采用小驼峰命名
多个组件都想要从本地存储获取指定的某个数据。需要将这个业务流程封装到hook里面。但凡遇到这个业务我们就不用重复的写本都存储代码
在src目录下面创建hooks文件夹/myhook.js文件
import React from "react" //获取本地存储的数据 export const useGetStorage = () => { const routes = JSON.parse(localStorage.getItem("routes") || "[]") return routes.filter(item=>item.auth) } //将数据保存到本地存储 export const useSetStorage = (array) => { localStorage.setItem("routes",JSON.stringify(array)) }
将本地存储的操作封装为hook,以后再任何一个页面中都可以使用这个hook
页面中使用这个hook函数
import React,{useRef} from 'react' import {useHistory} from "react-router-dom" import {useGetStorage,useSetStorage} from "../../../hooks/MyHook" export default function Sale() { useSetStorage([ {id:1,path:"/home/main",auth:true}, {id:1,path:"/home/user",auth:true}, {id:1,path:"/home/shop",auth:false} ]) const array = useGetStorage() const getData = ()=>{ console.log(array); } return ( <div> <button onClick={getValue}>获取文本框值</button> <button onClick={getData}>获取数据</button> </div> ) }
这个hook函数必须再组件最前面调用,否则React检测到hook不规范会抛出警告
自定义hook参数传递,和返回函数的封装
//将数据保存到本地存储 export const useSetStorage = () => { // localStorage.setItem("routes",JSON.stringify(array)) return (params)=>{ console.log(params); } }
面试题题:
-
你知道如何自定义hook吗?
-
你再项目中使用自定义hook来做了什么工作
React15-Redux的介绍
React15-Redux的介绍
一、概念
redux是一个专门用于做状态管理的第三方js库。并不是插件也不是组件。
可以集中管理我们项目中的状态(数据)
在我么React中组件通信我们可以使用父子通信、通信的技术来解决。如果遇到比较复杂的跨组件通信,可以引入状态机来进行数据管理
redux并不是只能应用react中。可以用到其他框架,包括vue也可以
组件通信流程:
状态机使用了过后,肯定增加代码的繁琐。
但是在复杂的业务场景下面,有了状态机能更加方便的进行数据管理。
当你不知道要不要用状态机的时候,就不要用。
但是数据比较多,通信比较复杂的时候,必须引入状态机来进行数据集中管理
二、redux的工作流程
官方提供了一张图片,可以查看redux的完整流程
-
从仓库中获取数据,研究如何在页面中获取数据
-
修改仓库中的数据,一旦变化页面就会更新
redux和react没有任何关系,我们可以在react引入redux来进行状态管理
-
单独研究redux工作流程
-
将redux和react结合起来使用
三、搭建环境
(1)在开发过程中要单独使用redux需要下载对应依赖
yarn add redux
(2)在src目录下面创建一个文件夹redux/store.js文件
/** * 这redux仓库代码 */ import {legacy_createStore as createStore} from "redux" // 创建一个仓库。里面存放我们的数据 const store = createStore(function(state={count:10}){ return state }) // 如何获取仓库数据 // 如何修改仓库数据
创建仓库createStore,在老版本里面可以直接使用,但是在新版本中createStore已经被废弃了。
通过legacy_createStore创建仓库。
(3)打印store这个仓库我们可以获取到两个非常重要api
getState:store仓库提供给我们进行仓库数据获取的api
dispatch:用于修改仓库数据的api
subscribe:一个监听函数,只要数据发生变化,可以监控到
(4)需要加载store.js这个文件
在index.js文件中引入store.js,项目启动加载仓库
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; // import 'antd/dist/antd.less'; import "./redux/store" const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> );
(5)获取仓库数据
/** * 这redux仓库代码 */ import { legacy_createStore as createStore } from "redux" // 创建一个仓库。里面存放我们的数据 const store = createStore(function(state={count:10}){ return state }) // (一)打印store console.log(store); // (二)如何获取仓库数据 console.log(store.getState());
(6) 修改仓库数据
/** * 这redux仓库代码 */ import { legacy_createStore as createStore } from "redux" // 创建一个仓库。里面存放我们的数据 const store = createStore(reducer) // (一)打印store console.log(store); // (二)如何获取仓库数据 console.log(store.getState()); // (三)如何修改仓库数据 // 修改仓库数据 /** * 流程:调用dispatch传递一个action通知对象给reducer,reducer进行修改 * reducer会将修改好的数据,返回给store */ const action = { // type属性是必须写的。值由用户自己来定 type: "INCREMENT", // payload名字是我们自己取的。值就是你要修改的内容 payload: 10 } // 创建一个reducer // reducer本身是一个函数,而且是一个纯函数 // 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数 function reducer(state = { count: 1 },action) { switch(action.type){ // 这个case进行累加的过程 case "INCREMENT": state.count+=action.payload return state default: return state } } // 通过dispatch进行仓库数据修改 store.dispatch(action) store.dispatch(action) // 获取到修改后结果 console.log(store.getState());
React16-Redux三大核心对象
React16-Redux三大核心对象
一、store
store是我们redux中非常重要的一个模块,代表仓库数据。
store仓库要创建新版本里面我们需要用到import { legacy_createStore as createStore } from "redux"
createStore
在创建过程中我们需要传递一个纯函数(reducer)
获取到一个仓库对象store
const store = createStore(reducer)
store.getState()获取仓库数据
store.dispatch()修改仓库数据
二、action
从开发的角度来说action就是一个普通对象。
action在redux中代表通知对象,这个通知对象里必须要由一个属性type
const action = { type:"名字自己定", payload:"传递参数" }
payload代表参数传递,名字可以自定义。约定就用payload代表处参数名字
以后只要页面数据要发送到redux修改都必须要用action来进行传递
三、reducer
仓库在创建的时候,第一次需要初始化,reducer就可以完成仓库初始化的任务
当我们有数据需要修改的时候,reducer也可以进行数据更新。得到结果返回给store仓库
function reducer(state = { count: 1 },action) { switch(action.type){ // 这个case进行累加的过程 case "INCREMENT": state.count+=action.payload return state default: return state } }
你们在进行仓库修改的时候,会有很多种修改方案。我们匹配你的方案来执行代码
修改仓库数据的唯一方案就是reducer的case来进行处理
特点:
-
reducer是一个纯函数,需要在创建仓库放在方法里面
-
reducer里会执行很多数据修改,每次修改完成后一定要返回一个newstate给仓库
修改后的reducer代码
function reducer(state = { count: 1 },action) { const {count} = state switch(action.type){ // 这个case进行累加的过程 case "INCREMENT": count+=action.payload return { ...state,count } default: return state } }
React17-Redux优化
React17-Redux优化
一、文件拆分
三大核心对象,我们需要在redux文件夹中进行拆分
在redux中创建reducers文件夹,设计一个CountReducer
// 创建一个reducer // reducer本身是一个函数,而且是一个纯函数 // 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数 export default function reducer(state = { count: 1 }, action) { let { count } = state switch (action.type) { // 这个case进行累加的过程 case "INCREMENT": count += action.payload return { ...state, count } case "DECREMENT": count -= action.payload return { ...state,count } default: return state } }
创建actions文件夹,里面设计CountAction.js文件
export const action = { // type属性是必须写的。值由用户自己来定 type: "INCREMENT", // payload名字是我们自己取的。值就是你要修改的内容 payload: 10 } export const action2 = { type: "DECREMENT", payload: 5 }
在仓库中我们需要用到内容引入
/** * 这redux仓库代码 */ import CountReducer from "./reducers/CountReducer" import { legacy_createStore as createStore } from "redux" import {action} from "./actions/CountAction" // 创建一个仓库。里面存放我们的数据 const store = createStore(CountReducer) store.dispatch(action) console.log(store.getState());
二、reducer拆分合并
我们在项目开发过程中reducer会有很多业务,不可能将所有的业务都写到一个reducer中。
将不同业务放在不同的reducer中去
在reducers文件夹里面创建不同的reducer文件
比如:CountRedcuer、UserRedcuer
// 创建一个reducer // reducer本身是一个函数,而且是一个纯函数 // 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数 export default function reducer(state = { count: 1}, action) { let { count } = state switch (action.type) { // 这个case进行累加的过程 case "INCREMENT": count += action.payload return { ...state, count } case "DECREMENT": count -= action.payload return { ...state,count } default: return state } }
userRedcuer
// 创建一个reducer // reducer本身是一个函数,而且是一个纯函数 // 纯函数:一个函数内部所有的执行不依赖外部数据,只能依赖他的参数。将这种函数称为纯函数 export default function reducer(state = { user:{id:1,name:"xiaowang"}}, action) { const {user} = state switch (action.type) { // 这个case进行累加的过程 case "UPDATENAME": user.name = action.payload return { ...state, user } default: return state } }
createStore这个函数在创建仓库的时候,默认只能接受一个redcuer
将拆分完成后的reducer进行合并
在redcuers文件夹下面index.js
import {combineReducers} from "redux" import UserReducer from "./UserReducer" import CountReducer from "./CountReducer" /** * 将多个reducer合并在一起 */ export default combineReducers({ UserRD:UserReducer,CountRD:CountReducer })
UserRD就是命名空间名字.以后再页面上要获取这个参数的时候,我们需要指定这个名字才能获取值
在store.js中引入合并后reducer
/** * 这redux仓库代码 */ import { legacy_createStore as createStore } from "redux" import {changeUsername,action,action2} from "./actions" import bigReducer from "./reducers" // 创建一个仓库。里面存放我们的数据 const store = createStore(bigReducer) // store.dispatch(action) console.log(store.getState().UserRD); store.dispatch(changeUsername) console.log(store.getState().UserRD);
三、action进行拆分合并
我们actions文件夹下面会定义很多的action的js文件,为了管理方便,我们可以将所有的action整合在一起
actions文件夹下面创建index.js将所有action合并在一起
export {action,action2} from "./CountAction" export {changeUsername} from "./UserActions"
以后页面上要使用任何一个action
import {action,changeUsername} from "./actions"
四、ActionCreator
我们在action中无法接受外部传递进来的参数.
暴露出去的内容需要换成actionCreator函数
/** * actionCreator * @param {*} num * @returns */ export const incrementAC = (num)=>{ return { // type属性是必须写的。值由用户自己来定 type: "INCREMENT", // payload名字是我们自己取的。值就是你要修改的内容 payload: num } } /** * action */ export const decrementAC = (num)=>{ return { type: "DECREMENT", payload: num } }
React18-函数组件使用redux
React18-函数组件使用redux
授课进程
一、在组件中使用react-redux
(1)载依赖
yarn add react-redux
(2)创建主仓库(在src/store/store文件中),将store仓库暴露出去
import {createStore} from "redux" import reducer from "./reducers/countReducer" const store = createStore(reducer); // React组件需要使用store仓库 export default store
(3)在index.js入口文件里面引入store
相当于Vuex暴露出去一个仓库,必须在main.js中注入到Vue对象
import store from "./store" new Vue({ store })
在React中也是一样的设计
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; // 导入仓库加载一次 import store from "./store/store" // Provider称为容器组件,直接和redux通信 UI无法直接和redux通信 import { Provider } from "react-redux" ReactDOM.render( <Provider store={store}> <App /> </Provider> , document.getElementById('root') );
提供一个Provider这个组件,将store仓库注入到Provide组件身上,目的是为了让所有组件都能够获取到store仓库的数据。
(4)来到组件中,引入hook函数
import React from 'react' import {useDispatch,useSelector} from "react-redux" import {incrementAC} from "../../../redux/actions" export default function ReduxComp() { const dispatch = useDispatch() // 获取仓库数据 const {user} = useSelector(state=>{ return state.UserRD }) const {count} = useSelector(state=>{ return state.CountRD }) const add = ()=>{ dispatch(incrementAC(20)) } const reduce = ()=>{ } return ( <div> <p>ReduxComp</p> <p>{user.id}</p> <p>count:{count}</p> <button onClick={reduce}>-</button> <button onClick={add}>+</button> </div> ) }
useDispatch:我们可以使用这个hook来获取dispatch对象
useSelector:可以获取仓库的数据。你需要进行解构
二、类组件要获取仓库数据
函数组件中我们可以使用第三方的hook
import {useDispatch,useSelector} from "react-redux"
但是类组件要获取redux仓库的数据,我们需要使用它connect高阶组件来实现
import React, { Component } from 'react' // connect是一个高阶 import { connect } from "react-redux" class ReduxClassComp extends Component { componentDidMount() { console.log(this.props); } render() { return ( <div> <p>{this.props.mycount}</p> </div> ) } } const mapStateToProps = (state) => { console.log("state",state); return { mycount: state.CountRD.count } } export default connect(mapStateToProps)(ReduxClassComp)
connect采用函数柯里化的形式进行参数传递。
第一个参数传递的回调函数,这个回调函数我们可以获取到仓库的数据
第二个参数传递当前组件,进行props内容赋值,当前组件props上面默认新增dispatch属性
React19-redux的中间件
React19-redux的中间件
一、redux目前问题
目前redux在使用过程中我们发现的一个问题:
redux默认无法处理异步请求的。
我们可以在页面中发送请求,将数据更新到redux中,实现其他页面的数据共享。
这种方式需要我们页面自己来维护异步请求。
二、中间件概念
redux这个js库专门用于状态管理的一个库。
目前提供的核心业务可以满足我们对状态的管理。
功能还不止于此,还采用中间件的设计方式来对redux功能进行加强/
目前在市面上,我们使用redux的时候,除了使用redux提供核心流程来处理业务,你还可以给redux新增第三方中间件来加强他的功能。
当我们redux没有使用中间件的时候流程:
在redux状态机中新增中间件流程如下:
mid1:我就发送异步请求。得到结果后,交给dispatch进行派发给reducer
在redux中常见的中间件有下面几种:
-
redux-logger:记录状态机数据修改的日志中间件
-
redux-thunk:让我们在dispatch派发请求到reducer的时候,执行异步请求
-
redux-saga:可以在redux-saga中间件里面执行异步请求,得到结果后结果dispatch派发reducer
(迭代器和生成器)
学习redux-thunk,这个也是目前在企业开发过程中经常使用的一个中间件
三、使用中间件
redux-logger
用于记录日志的。只要你的redux数据发生变化,我们可以使用这个中间件来获取数据
(1)下载中间件依赖
yarn add redux-logger --dev
(2)在store仓库中配置中间件
/** * 这redux仓库代码 */ import { legacy_createStore as createStore,applyMiddleware } from "redux" import {changeUsername,incrementAC,decrementAC} from "./actions" import bigReducer from "./reducers" import logger from "redux-logger" // 创建一个仓库。里面存放我们的数据 // 创建仓库的时候,可以指定加载哪些中间件 const store = createStore(bigReducer,applyMiddleware(logger)) export default store
使用这个中间件过后,我们默认可以在控制台上面打印你们每次redux数据操作的日志
下载redux-logger插件依赖的时候,—dev
这个包只能在开发过程中,打包后这个包不需要。
redux-thunk
可以在redux中处理异步请求
以后你可以将页面异步请求代码交给redux-thunk来进行处理,更新到store仓库中
类似于vue中的actions板块
(1)下载对应依赖
yarn add redux-thunk
(2)在我们store.js中引入我们中间件,进行加载
/** * 这redux仓库代码 */ import { legacy_createStore as createStore,applyMiddleware } from "redux" import {changeUsername,incrementAC,decrementAC} from "./actions" import bigReducer from "./reducers" import logger from "redux-logger" import thunk from "redux-thunk" // 创建一个仓库。里面存放我们的数据 // 创建仓库的时候,可以指定加载哪些中间件 // 按照配置的顺序来加载,先进入第一个,执行完毕后再进入第二个 const store = createStore(bigReducer,applyMiddleware(logger,thunk)) export default store
多个中间件执行顺序是按照我们配置的顺序来进行的。
(3)页面上完成业务开发
没有redux-thunk中间件的,我们disptach只能接受一个action对象作为传递参数。
有了redux-thunk中间件,页面dispatch可以接受一个函数作为我们参数进行派发。
在函数中间进行异步请求
import React from 'react' import { useDispatch, useSelector } from "react-redux" import {initRoleAC,asyncInitRoleAC} from "../../../redux/actions" export default function ReduxRolesComp() { const dispatch = useDispatch() const {roles} = useSelector(state =>{ return state.RoleRD }) /** * redux-thunk这个中间件运行流程 * 页面派发disptach,需要一个action */ const fetchRole = ()=>{ dispatch(asyncInitRoleAC()) } return ( <div> <h3>ReduxRolesComp</h3> <p>{roles.length}</p> <button onClick={fetchRole}>获取所有的role</button> </div> ) }
asyncInitRoleAC代码如下:
import {findAllRoles} from "../../apis/roleApi" // 这个action用于通知reducer进行数据更新 export const initRoleAC =(array)=>{ return { type:"INITROLE", payload:array } } // 这个actionCreator可以支持异步请求 export const asyncInitRoleAC = ()=>{ return async (dispatch)=>{ const res = await findAllRoles() dispatch(initRoleAC(res.data)) } }
有了redux-thunk这个中间,针对dispatch对象进行了增强,可以让dispatch接受一个函数来执行异步请求
传递进去dispatch,在内部调用,再将结果传递给reducer
类似于Vuex中提供了actions模块,异步请求都放在这个模块中进入。统一管理所有异步请求
React20-redux调试工具安装
React20-redux调试工具安装
针对React组件开发,我们可以安装一个chrome工具来调试组件的数据
针对Redux开发,我们也可以安装一个Redux-dev-tooks工具来进行调试
开发步骤: (1)需要在浏览器中将dev-tools插件安装完成
将插件拖入chrome的扩展程序里面
安装完毕后,并不能redux插件中查询到store数据
(2)在代码中下载插件,用于显示数据到浏览器dev-tools工具
yarn add redux-devtools-extension
需要在我们的代码中引入这个插件,并加载这个插件
/** * 这redux仓库代码 */ import { legacy_createStore as createStore,applyMiddleware } from "redux" import bigReducer from "./reducers" import logger from "redux-logger" import thunk from "redux-thunk" import { composeWithDevTools } from 'redux-devtools-extension'; // 创建一个仓库。里面存放我们的数据 // 创建仓库的时候,可以指定加载哪些中间件 // 按照配置的顺序来加载,先进入第一个,执行完毕后再进入第二个 const store = createStore(bigReducer,composeWithDevTools(applyMiddleware(logger,thunk))) export default store
需要在创建仓库的时候,将插件放到第二个参数中。中间件也应该放在插件中
所有的数据操作,我们都可以浏览器中进行检测。
React21-性能优化
React21-性能优化
一、函数组件的更新
在父子组件中,React默认父组件更新过后,子组件也会跟着更新
某些情况父组件更新,子组件没有数据更新,子组件还是会刷新一次,造成我们性能浪费
这种情况我们可以考虑在子组件使用React.memo来进行组件的包装
import React, { useEffect } from 'react' import Children2 from './Children2'; function Children(props) { // 组件第一次加载的会运行,当你组件加载完毕,不会再执行 useEffect(() => { console.log(123); }, []) useEffect(()=>{ console.log(345); }) return ( <div> <h3>Children</h3> <p>{props.username}</p> <button onClick={()=>props.changeUsername("xiaofeifei")}>修改父组件username</button> <Children2></Children2> </div> ) } export default React.memo(Children)
只要子组件props没有接受到新的值我们就不会更新子组件
如果props的值发生变化,子组件肯定更新一次
但是如果父组件传递了一个函数给子组件,不管父组件那边更新的是哪个数据,子组件依然会跟着跟新
原因:父组件更新一次,函数会被重新定义,函数重新创建一次地址会发生变化,子组件检测数据变化。更新一次。
解决方案:
import React, { useState,useCallback } from 'react' import Children from './Children' export default function ParentComp() { const [count,setCount] = useState(10) const [username,setUsername] = useState("xiaowang") const changeCount = ()=>{ setCount(30) } // const changeUsername = (val)=>{ // setUsername(val) // } // useCallback返回一个函数 // 如果检测到你监控的数据没有发生变化,不会产生一个新的函数。返回之前缓存的函数 const changeUsername = useCallback((val)=>{ setUsername(val) },[username]) return ( <div> <h3>ParentComp</h3> <p>{count}</p> <button onClick={changeCount}>点击修改</button> <Children changeUsername={changeUsername} username={username}></Children> </div> ) }
我们父组件可以使用useCallback来定义函数,将useCallback定义的函数传递给子组件。
useCallback默认有缓存功能,当我监控的数据没有发生变化的时候,这个函数不会重新创建一次。
二、类组件的更新
我们类组件中可以使用shouldComponentUpdate
进行数据判断
当props数据没有发生变化的时候,默认返回false,当发生变化的时候,默认返回ture
当然官方已经给我提供了一个PureComponent组件,这个组件可以满足我们页面在没有更新props的情况下,默认不会更新页面。
注意:PureComponent内部已经实现shouldComponentUpdate
逻辑。但是只能浅对比。
如果传递过来的是一个对象,检测对象地址是否发生变化,地址没有变化,检测数据没有变化。地址里面的内容检测不到。
--------------------------------------------------------------------------上下合并符号--------------------------------------------------------------------------
面试题08-React性能优化方案
React中的问题
有时候组件的render会在没有必要的情况下调用就会带来性能问题。
当你state或者props没有更新操作,父组件也没有重新渲染的情况下,如果render一次,得到跟虚拟dom一样的结果。
shouldComponentUpdate
使用shouldComponentUpdate和PureComponent达到的效果是一样的。
shouldComponentUpdate(nextProps,nextState){ if(nextProps === this.props && nextState===this.state){ return false } return true }
如果你的组件中不需要对比state或者props就需要更新,你将if里面的判断去掉就行。
传参优化
父子组件传递参数的时候,我习惯结构过后来使用
class Student extends Component{ render(){ const {msg} = this.props return( <div>{msg}</div> ) } }
父组件的定义
class Parent extends Component{ state = { user:{ id:1, username:"xiaowang", password:'123' } } render(){ return( <Student user={this.state.user} {...this.state.user}></Student> ) } }
避免在父组件中将属性展开过后传递给子组件。这样会带来更多的性能消耗。
key定义
对于数组数据,在进行遍历渲染的时候要求对每个遍历后的节点都需要增加一个key,并且这个key必须是唯一的,而且不要用index来作为key。
虚拟DOM上面增加的标准,diff算法来进行虚拟dom对比的时候,使用这个key作为参考条件。
分片打包
路由对应组件的懒加载
-
loadable插件可以实现组件的懒加载,第三方
-
React.lazy()也可以实现组件的懒加载,官方
这两种发方式,自己能在笔记上写出来。
Fragment
当你的子组件需要一个父组件的时候,你可以使用React.Fragment来定义一个父标签。
或者使用空标签来定义父标签。进来减少额外标签的使用达到性能优化。
render(){ <> <h1>h1<h1> <h1>h1<h1> </> }
render(){ <React.Flagment> <h1>h1<h1> <h1>h1<h1> </React.Flagment> }
Vue 的template 小程序的 block
组件卸载和加载
进来减少组件的创建和销毁,尤其是针对经常使用的组件。调整CSS样式来对组件进行隐藏,hidden属性。控制组件的css样式,display:none
计算缓存
使用函数组件的useMemo、useCallback这两个hook,也能达到计算缓存的作用。
TypeScript01-环境搭建
TypeScript01-环境搭建
TypeScript目前企业中也在逐渐使用。一般配合我们的框架来进行使用
React+TS开发、Vue3+TS开发
一、基本概念
TypeScript编程语言是微软推出的一款开源的开发语言
TypeScript是JavaScript的超集。TypeScript也是基于JavaScript来进行开发的,具备JavaScript所有特性,支持完整的ES6\ES5等等语法。
学习TypeScript就相当于在学习JavaScript,更多要学习的TypeScript中编程规范
比如TypeScript中我们可以进行数据类型约束,一旦有了约束后我们程序设计会更加规范
二、JavaScript特点
弱类型语言:
var i = 10 var k = "xiaowang"
在JS这门语言中定义变量无需,申明数据类型。数据类型是根据的值来进行确定
typeof i ===>number typeof j ===>string
如果我对i这个变量进行了修改
i = "xiaofeiei" typeof i ===>string
强类型语言:比如Java、C++等等
int i = 10 --->int代表数据类型,整数类型
一旦强类型语言在定义变量的时候,数据类型确定了,以后只能赋值相同数据类型的结果
i = "xiaowang" //程序会报错
强类型语法和弱类型语言区分:
类类型语法定义变量的时候,明确这个变量数据类型,弱类型语言一般不会明确他的数据类型
三、JavaScript缺点
JavaScript是弱类型语言,导致你们很多代码在运行过程中才会发生报错。
无法在写代码的时候,就检测到错误
const user = { username:"xiaowang", age:"12" }
在代码中要使用user中的数据
user.username user.classes.id //编写代码编译器不会抛出任何错误,代码必须在浏览器运行抛出错误
函数要接受外部参数
function show(array){ array.forEach(item=>{ item.state = true }) } show([]) show("xiaowang") //调用别人的函数,传递参数传递错误,代码只能运行的时候,抛出异常
如果我们能够在编写代码的时候,就检测到很多的错误。可以让代码设计更加严谨、代码健壮性会好
四、TypeScript能解决的问题
TS是JS的超集,完全遵循我们ES5、ES6的标准。你写TS代码就是在写JS代码。提供了更多的约束。更多规范要求我们写代码必须按照规范来写,否定编译不通过。
TypeScript代码——-编译——JavaScript
这个编译过程检测你的语法是否正确,如果语法有问题,规范不正确,那就会抛出异常。
减少代码在运行时报错
五、搭建环境
(1)保证电脑安装nodejs
(2)全局安装TypeScript
npm install typescript -g tsc -v
(3)创建一个项目,在项目中编写ts代码
// 默认可以在TS代码中编写JS代码,能够正确识别 // 在TS规范中,我们定义变量需要明确这个变量的数据类型 // 以后在框架写的TS代码,最后打包的TS代码编译为JS代码,运行也是JS代码 var username:string = "xiaowang" let age:number = 10 console.log(username); console.log(age); age = 20
(4)你可以将指定的ts文件编译为js文件
tsc index.ts --->默认同级目录下面创建index.js文件
六.配置当前项目自动编译
我们tsc可以手动将ts文件编译为js文件,但是比较麻烦.
每次写了ts后,手动tsc编译,想要自动编译.代码写完,自己生成js文件
(1)在项目中初始化一个ts配置文件
tsc --init
执行完这个命令后,我们项目中会出现一个tsconfig文件,这个文件里面存放的就是ts配置信息
{ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } }
在配置文件中将以下内容配置完成
"outDir": "./js",
你将ts编译为js代码的时候,默认将js代码放在项目js目录下面
在vscode工具中进行自动编译配置流程如下:
vscode终端——运行任务—-typescript—-监视xxx任务.
以后你这个项目中ts代码只要有更新,自动将ts代码编译为js代码.
TypeScript02-数据类型
TypeScript02-数据类型
TypeScript强类型语言,在定义变量的时候必须要指定数据类型是什么?
首先得知道目前我们TypeScript有哪些数据类型
一.数字类型
var price:number = 20 price = true //报错.必须指定price值为number才能编译通过
二.字符串类型
var username:string = "xiaowang" username = "xiafoefie"
三.布尔类型
var flag:boolean = true flag = false
boolean类型的值只能true\false.
四.数组类型(重要)
语法一:
// 语法一 let array:number[] = [] array.push(1) array.push(2) // array.push("xiaowang") let array2:string[] = [] array2.push("xiaowang") array2.push("1") // array2.push(1)
定义数组的过程中,数据类型一开始就定下来,以后这个数组就只能存放对应的数据类型
语法二:
let array3:Array<number> = [] // array3.push("xiaowang") array3.push(1)
使用到了泛型,Array代表数组类型,``数组里面存放的number数据类型
五.undefined和null
/** * undefined 和 null * 在TS中,变量没有赋值之前不能使用 * a的数据类型要么number,要么undefined */ let a:number | undefined; console.log(a); a = 20 let b:string | null = null b = "xiaowang"
在TS中我们一般用undefined和null来表现我们可能出现的特殊情况.
六.元组类型(了解)
元组类型是数组类型一种衍生,扩展.
数组类型默认只能存放统一数据类型的值.要么全是number\要么全是string类型
如果我一个数组要存放多个数据类型,可以考虑用元组来实现
/** * 元组类型 */ let point:[number,string,boolean] = [1,"xiaowang",true]
元组类型就是将你们数组中每一个位置的数据类型定义出来
七.枚举类型
enum就是我们TS中提供枚举类型.
场景:比如你们项目中会有订单,订单会有状态.一个订单会有很多个状态.
以前的JS代码:
const order = { id:1, name:"小米耳机", state:1 } if(order.state==1){ }else if(order.state==2){ }
上面这段代码非常不规范,用到魔法数字(魔鬼数字)
猜测1代表什么意思\2代表什么意思
相当于要将魔鬼数字1\2\3\4转化为能够可读的代码
TS代码优化
/** * 枚举类型 */ enum orderState { paySuccess=1, payError=2, payTimeout=3 } // 后端传递回来订单 const order = { id:1, name:"小米耳机", state:1 } if(order.state == orderState.paySuccess){ }else if(order.state == orderState.payError){ }else if(order.state == orderState.payTimeout){ }
前提:枚举类型里面状态必须是有限的.一旦 写了以后代码就可以直接用枚举中的状态.
代码解构会更加清晰
八、any类型
表达任意类型的一个基础类型
let age:any = "xiaowang" age = 20 //any意义就不大
any代表任意类型,主要用于表达哪些无法用准确的数据类型来进行表示的场景
const element:any = document.getElementById("app")
比如我们要获取页面上某个节点,这种情况下我们不好用数据类型来表达,为了满足ts的规范,我们可以使用any来进行类型声明/
定义一个数组,这个数组里数据可以有多种类型
let array:any = [1,"xiaowangf",true] //编译没有问题
九、never类型(了解)
never类型表示哪些永远不存在的值
never类型的应用场景比较局限,主要表达哪些总会抛出异常或者不会有返回值的函数表达式或者箭头函数。
let others:never; others = (()=>{ throw new Error("错误") })()
十、object类型
在TS开发过程中object类型可以表达三种结果
表示值为{}、也可以[]、还可以function
/** * object数据类型 */ let obj1:object = {} let obj2:object = [] let obj3:object = function(){ } let obj4:object = {id:1} console.log(obj4.id); //报错,obj4找不到id属性
如果我定义一个对象,明确告诉TS代码,我的对象里面有哪些东西
let student:{id: number;name: string} student = {id:1,name:"ddd"}
TypeScript03-函数的定义
TypeScript03-函数的定义
TS中函数定义和JS是一模一样的,可以是普通函数申明,也可以函数表达式
但是在TS中针对函数做了很多的约束、函数的参数进行约束、函数的返回值进行了约束
一、函数参数
我们定义一个函数,参数要接受外部的值
// 普通函数定义 function show(params:number,params2:string){ console.log(params); console.log(params2); } show(1,"xiaowang")
在函数形参进行了约束,在传递值的时候,就必须按照指定的参数类型传递
-
参数个数必须满足要求
-
每个位置参数,数据类型一定要匹配
参数是一个对象
const play = (user: { id: number }) => { console.log(user.id); } play({ id: 1 }) const play2 = (stus:string[]) => { console.log(stus); } play2(["1","2"])
参数是可变参数
const play3 = (params:number,...params2:number[]) => { console.log(params); // 1 console.log(params2); // [2,3,4,5,6,7,8] } // 表示可变参数:参数的个数可以动态变化 play3(1,2,3,4,5,6,7,8)
二、函数的返回值
你在TS中定义每个函数都应该有返回结果。
就算函数不需要返回,我们也应该告诉调用者,当前函数没有返回结果
// void代表当前函数没有返回结果,不写默认编辑器添加 function eat(num1:number):void{ console.log(num1); return; } eat(10) function eat2(num1:number,num2:number):string{ const result:string = num1 + num2 + ""; return result } const total2:string = eat2(20,30)
TypeScript04-接口编程
TypeScript04-接口编程
前后端进行数据交互的时候,后端要提供数据接口。
接口本质上是一种规范,其实就算针对后端返回的数据进行了规范约定。
针对前端代码进行一些数据约束,接口编程。
配置一台电脑,首先购买主板,主板上很多接口、内存的接口、硬盘接口、CPU接口/
不同厂家或者不同生产厂商都回遵循一定的标准来设计产品。这个标准按照接口标准来设计。
在TS中接口编程主要就算针对前端的一些数据、前端一些业务进行标准约束。将这种称为接口编程
接口的分类:
-
属性类型接口:使用接口来约束我们的属性
-
函数类型接口:使用接口来约束我们函数的参数或返回值
-
可索引接口(扩展)
-
类类型接口:在面向对象中会接触到,用接口来进行面向对象开发
一、属性类型接口
定义一个对象,这个对象有指定的属性
编号:number
名字:string
年龄:number
// 使用接口来对user进行约束 // 只要interface来定义,变量名字首字母I // 接口设计了一个公共的规范(定义了新的数据类型) interface IUser { id:number, name:string, age:number, array:number[] } const user:IUser = { id:1, name:"xiaowang", age:20, array:[1,2,3] } function show(user:IUser):void{ console.log(user); } show(user)
接口里面可以设置可变的约束
/** * 针对一些场景进行设计 */ interface IStu { id:number, name:string, address?:string //这个属性非必须,可有可无 } const student:IStu = { id:1, name:"xiaowang", address:"wuhouqu" } const student2:IStu = { id:2, name:"xiaofeifei" }
?:
在接口中代表当前这个属性非必须的,如果你要使约束,必须按照我们类型类进行约束
二、函数类型接口
函数类型接口,我们主要针对函数的参数和函数的返回值进行约束
/** * 使用接口来对函数进行约束 * show函数要接受一个对象 */ interface IUser { id:number, name:string } function show(params:IUser):void{ console.log(params); } const user:IUser = { id:1, name:"xiaowang" } const user2:{id:number} = { id:1 } show(user) //show(user2) //编译报错 /** * 写一个函数,这个函数我要接受一个数组作为参数 * 数组里面装的对象 * 对象数组,数组里面存放指定的对象 */ interface IClasses { id:number, name:string } function play(array:IClasses[]):IClasses{ return array[0] } const data = [ {id:1,name:"xiaowang"}, {id:2,name:"xiaofei"}, {id:3,name:"xiaoliu"}, ] play(data)
复杂数据类型,接口嵌套
/** * 复杂的数据类型 * 定义一个班级,每个班级有一个老师,每个班级有多个学生 */ // 定义一个接口约束teacher interface ITeacher { id:number, name:string, type:string } interface IStudent { id:number, name:string } interface IClasses2 { id:number, name:string, teacher:ITeacher, stus:IStudent[] } const classes:IClasses2 = { id:1, name:"web21", teacher:{id:1,name:"bobo",type:""}, stus:[ {id:1,name:"小王"} ] }
三、可索引接口(了解)
/** * 可索引接口 */ interface IArrindex { // index代表数组的下标,默认为number [index:number]:string } const arr:IArrindex = ["xiaowang","xiaozhang"]
四、类,类型接口
类类型接口,需要在面向对象开发过程中才能使用。
目前暂时还没有讲到面向对象。
五、在接口中定义函数类型
type successType = (msg:string)=>void // 用于定义函数类型 type error = (error:string)=>void interface IType { url:string, method?:string, data:newData, success:successType, error?:()=>void }
在接口中要定义函数类型,我们可以函数名字:error:()=>number
练习题:
定义一个数组,里面存放用户信息。两种不同类型的用户(普通管理员和超级管理员) 普通管理员(id、name、role、dept, age) 超级管理员(id、name、role、menus:[{id :1,path:"/home"}],age) 定义两个函数,传递一个数组进去, 1、返回所有的超级管理员 2、求年龄最小的用户是谁
泛型编程
TypeScript05-类型断言
TypeScript05-类型断言类型推断
一、类型断言
普通断言
TS会自动进行类型推导,比如你的一个函数没有明确声明返回结果,编辑器会自动推断你的返回结果类型
function show(){ return 12 }
自己来进行类型推断,肯定有些地方部适用于我们程序,比如下面的代码
// 普通管理员 interface IUser { id: number, name: string, role: string, dept: string, age: number } interface menu { id: number, path: string } // 超级管理员 interface IManager { id: number name: string menus: menu[] age: number role: string } // 将这两个约束合并在一起。同时作用域myUser // type称为类型声明,你可以指定一个新的数据类型 type members = IUser | IManager let myUser: members[] = [ { id: 1, name: "xiaowang", role: "普通管理员", dept: "研发部", age: 23 }, { id: 2, name: "xiaofei", role: "普通管理员", dept: "市场部", age: 24 }, { id: 3, name: "xiaoliu", role: "超级管理员", age: 20, menus: [{ id: 1, path: "/home" }] } ] // 定义一个函数,返回所有的超级管理员 function findUser(params: members[]): void { const result = params.filter(item=>{ return item.menus //报错 }) }
item.menus获取这个属性,编辑器就会报错,推断出来item可能没有menus属性
使用类型断言
function findUser(params: members[]): void { // 需要类型断言,告诉编辑器,我知道自己在干什么 const result = params.filter(item=>{ return (<IManager>item).menus }) }
还可以继续下面这种语法
function findUser(params: members[]): void { // 需要类型断言,告诉编辑器,我知道自己在干什么 const result = params.filter(item=>{ return (item as IManager).menus }) }
非空断言
?.
这个运算符号是ES2020这个版本推出的一个语法,可以判断符号前面的元素是否可用
// 如果myAllState是一个字符串,任何一个字符串能够toString() let myAllState:null | undefined | string // 检测你myAllState是否为null或者undefined。?.就不会继续往后执行代码 myAllState?.toString()
TS也提供了这种非空判断
myAllState!.toString()
在定义函数的时候,我们可以针对函数类进行断言
type myFun = ()=>number function show(callback:myFun | undefined){ // const num1 = callback() const num2:number = callback!() } show(undefined) show(function(){ return 123 })
?.
这个才是完整的表达式,ES2020(ES11)的新增的特性,正好应用在ts里面
!
这个是可以独立使用的。这个是TS提出的概念
二、类型别名
联合类型
类型别名经常用于给一个类型起一个新的名字,一般用于联合类型
场景一:
type message = number let username:message = "xiaowang"
场景二:
联合类型,一个类型可以将多个类型合并起来表达
type message = number | string | undefined let password:message = "xiaowang" password = true //报错。password无法赋值为boolean类型
以后你可以将type拿来定义一种新的数据类型,将这个类型拿去进行使用
交叉类型
如果有两个类型,在使用过程中想要合并在一起,我们可以用交叉类型
最基础的语法
type users = number & string
users的数据类型是将number和string交叉的部分合并起来
这种代码没有太多的意思,number和string本来就没有交叉的部分
真正的引用场景
/** * 交叉类型 */ // type users = number & string type mixinType = {id:number} & {name:string} // minixType最终结果等于 {id:number,name:string} let data:mixinType = { id:1, name:"xiaowang" }
还可以合并引用类型
interface IA { obj:{ a:boolean } } interface IB { obj:{ b:string } } type AB = IA & IB let result:AB = { obj:{ a:true, b:"xiaowang" } }
思考?到底interface 和 type有什么区别?
区别:
两种方式都可以用来定义我们的数据类型,interface一般专注于对某个具体对象进行约束。
type一般用于针对多个类型可以合并的情况,重新定义新的类型。
type来管理多个interface
练习:
封装一个ajax网络请求。
ajax函数需要接受外部的参数是一个对象
ajax({ url:"地址", method:"GET", //可有可无 ,没有传递默认GET data:{id:1,name:"xiaowang"} | "id=1&name=xiaowang", success(msg){ console.log(msg) }, //可有可无。 error(){ } }) function ajax(obj){ }
// 答案 // 成功函数类型 type SuccessType = (msg: string) => void; interface IObj { url: string; method?: string; data: IData; success: SuccessType; error?: (err: string) => void; } type IData = string | IObjData; interface IObjData { id: number; name: string; } // 定义函数 function ajax({ url, method = "GET", data, success, error }: IObj) { const xmlhttp = new XMLHttpRequest(); xmlhttp.open(url, method); const res: string = changeDataToString(data); console.log(res); xmlhttp.send(res); xmlhttp.onreadystatechange = function () { if (xmlhttp.status == 200 && xmlhttp.readyState == 4) { const res = xmlhttp.responseText; success(res); } else { const res = xmlhttp.responseText; error!(res); } }; } ajax({ url: "http://127.0.0.1", method: "GET", data: { id: 1, name: "xiaowang" }, success(msg) { console.log(msg); }, error(err) { console.log(err); }, }); function changeDataToString(data: any) { let str: string = ""; let arr: string[] = []; for (let key in data) { str = `${key}=${data[key]}`; arr.push(str); } return arr.join("&"); }
TypeScript06-泛型编程
TypeScript06-泛型编程
一、TS开发过程中遇到的问题
比如我现在要设计一个函数,这个函数接受两个参数,将两个参数的累加起来,返回给外部
function computed(params1:number,params2:number):number{ const result = params1 + params2; return result }
又来了一个需求,将两个字符串加起来,返回到外面
function computed2(params1:string,params2:string){ const result = params1+params2 return result }
因为我们在设计函数的时候,我们需要定义函数参数类型。这种情况下,我们只能针对不同的参数,来设计不同的函数。
泛型这个知识点就是用于解决参数无法动态变化的问题
二、函数泛型编程
采用泛型编程来解决我们上面的问题
function play(params1:number,params2:number){ return params1+params2 } function play2<T>(params1:T,params2:T):T{ return params1 } play2<number>(12,34) play2<string>("xiaowang","xiaozhang")
``这个代表的占位符,T代表的是数据类型。
以后你要传递参数给play函数,我们可以自己来定义参数是什么类型
T 这个名字并不是固定的,你可以换成其他名字。一把要求大写
function play3<K>(address:K){ const newAddress:K = address }
练习:编写一个函数,接受一个数组,返回数组中最大值
/** * 使用泛型来解决问题 */ function getMaxValue3<T>(arr:T[]):T{ let max = arr[0] arr.forEach(item => max = item > max ? item : max) return max } const mydata = [1,4,7,9,0] const value = getMaxValue3<number>(mydata) console.log(value); const value2 = getMaxValue3(['a','b','c']) console.log(value2);
当我们代码在运行过程中,我们就能够知道T到底是什么数据类型。
三、类的泛型编程
// 函数的泛型,就在调用这个函数的时候,我们可以指定函数泛型的值 // 类的泛型,在定义class的时候,也可以使用泛型来占位.创建对象在确定 /** * 使用面向对象的语法来获取数组中最大值 */ class Compare<T>{ // 类里面定义了一个属性,这个属性的类型为T list: T[] = [] // 往list数组装我们数据 add(num: T) { this.list.push(num) } max(): T { let max = this.list[0] this.list.forEach(item => max = item > max ? item : max) return max } } const comp = new Compare<number>() comp.add(1) comp.add(2) comp.add(3) console.log(comp.max()); const comp2 = new Compare<string>() comp2.add("a") comp2.add("b") comp2.add("c") console.log(comp.max());
在定义类的需要加上``代表当前类接受的外部类型
let array:number[] = [1,2,3] let array:Array<number> = [1,2,3]
四、定义多个泛型
一个函数或者一个类,有可能会使用多种数据类型进行定义。多个数据类型都是可变的,我们可以传递多个占位
/** * 多个泛型 */ function computedData<T,K,M>(arg1:T,arg2:K):T{ console.log(arg1); console.log(arg2); let newpar:T = arg1 return arg1 } computedData<number,string,number>(1,"xiaowang")
你可以在<>
中定义我们的多个泛型,获取到结果后赋值内部的变量
五、泛型约束
代码如下:
/** * 泛型约束 * T目前没有明确声明他是什么类型.导致arg获取里面内容.检测不到 * 传递一个对象进去,获取对象里面length属性 */ function tranalate<T>(arg:T){ console.log(arg.length); } interface IMyUser { id:number, name:string, length:number } const user:IMyUser = { id:1, name:"xiaowang", length:2 } tranalate<IMyUser>(user) // tranalate<number>(1)
目前遇到的问题,使用泛型来定义数据类型,我们发现无法推导出参数的类型是什么。
因为T就是属于变化的部分。
有可能在使用过程中并没有传递对象,并且还有length属性
对泛型进行一些约束
/** * 泛型约束 * T目前没有明确声明他是什么类型.导致arg获取里面内容.检测不到 * 传递一个对象进去,获取对象里面length属性 */ function tranalate<T extends IMyUser>(arg:T){ console.log(arg.length); console.log(arg.name); console.log(arg.id); // 类型断言 // console.log(arg.address); } interface IMyUser { id:number, name:string, length:number } interface IMyUser2 { id:number, name:string, length:number, address:string } const user:IMyUser = { id:1, name:"xiaowang", length:2 } const user2:IMyUser2 = { id:1, name:"xiaowang", length:2, address:"武侯区" } tranalate<IMyUser>(user) tranalate<IMyUser>(user2)
我们在设计代码过程中,我们使用extends来进行泛型的继承
<T extends IMyUser>
传递进来任何类型,我们都可以以IMyUser作为我们参考。
为什么不直接使用IMyUser来定义类型
直接写死这种有可能会出现类型丢失情况。不利于扩展我们代码
六、泛型工具类型
为了开发者方便使用TS,内置了一些常见的工具或者代码。
typeof类型
typeof是用来检测基本数据类型的运算符。
在js中我们可以使用typeof检测基本类型
let a = 10 typeof a ==>number
在ts中我们typeof应用范围更加广泛
interface IPeople { name:string, age:number } const polic:IPeople = { name:"wangsir", age:23 } // police实例化过后的对象,我可以根据实例化后的对象,得到当前他的数据类型 type man = typeof polic //man = { name:string,age:number} const teacher:man = { name:"wangsir", age:23 }
你甚至可以从一个已经实例化的对象中得到他的约束类型。作为一个新的数据类型来使用
真实业务场景:比如别人给你传递一个对象过来。你可以通过这个对象,构造一个跟他数据类型一模一样的对象。
甚至typeof还可以推断出我们复杂的对象
const mystudent = { id:1, name:"xiaowang", age:20, classes:{ id:1, name:"五年" } } type newType = typeof mystudent
typeof推断出来的数据类型
type newType = { id: number; name: string; age: number; classes: { id: number; name: string; }; }
keyof
keyof这个关键字在TS中可以获取到我们指定对象中所有的属性名字。
问题:
function searchTeacher(arr:ITeacher[],key:string,value:string){ // for (let index = 0; index < arr.length; index++) { // if(arr[index][key] == value){ // return arr[index] // } // } const res = arr.filter(item=>{ if(item[key] == value){ return true }else{ return false } }) } searchTeacher(teahcer,"type","数学")
item[key]
报错,当前我们key是一个变量,无法明确告诉编译器这个变量的值范围是什么。
keyof这个关键字是TypeScript2.1版本引入的一个模块,这个操作符表达意思用于获取某个数据类型的键。返回一个联合类型,这个联合类型里面存放的就是所有的key
interface IPeople { id:number, name:string } const array = [ {id:1},{id:2} ] type K1 = keyof IPeople // id | name type K2 = keyof IPeople[] // length | toString | push | pop 等等 type K3 = typeof array[0] // id
解决上面遇到的问题。动态获取一个对象中指定的属性。
解决方案1:
interface IObj { id:number, name:string, address:string } //将obj对象进行断言,赋值为any.不推荐 function ObjectKey(obj:IObj,key:string){ return (obj as any)[key] }
将obj断言为any类型,让程序能够知道我们操作的一个any类型的对象,获取any不再严格校验
这个方案我们不推荐使用
解决方案2:
interface IObj { id:number, name:string, address:string } // 如果key值能明确下来. key = id | name | address // key as keyof IObj = key = id | name | address function ObjectKey(obj:IObj,key:string){ return obj[key as keyof IObj] }
解决方案3:
function objectKey2<T,K extends keyof T>(obj:T,key:K){ return obj[key] } objectKey2({id:1,name:"xiaowang",address:"wuhouqu"},"name") objectKey2({id:1,name:"xiaowang",address:"wuhouqu"},"address")
K extends keyof T
k的结果应该来自于我们T这个约束的key。对象里面有多少属性,我们就可以传递多少的key值进行匹配
TS掌握到什么程度。写Vue3+TS
你开发过程中,如果想要看某些源码。源码采用TS里面的一些内容表达什么意思
TypeScript07-面向对象编程
TypeScript07-面向对象编程
一、类的定义
class Student{ // 类的属性必须要明确声明 属性:数据类型 id:number name:string constructor(id:number,name:string){ this.id = id this.name = name } show(msg:string):void{ } } export default {}
类的属性不能动态生成,必须要提起将属性定义出来
二、类的属性和行为
静态属性和静态行为
class Student{ // 类的属性必须要明确声明 属性:数据类型 id:number name:string // 类的属性 static address:string = "高新区" constructor(id:number,name:string){ this.id = id this.name = name } show(msg:string):void{ } static play(){ } } const stu = new Student(1,"xiaowang") console.log(stu.name); console.log(Student.address); console.log(Student.play());
静态属性和静态行为,我们也称为类属性和类行为,只能通过类名来进行调用。
三、类的继承
/** * 访问修饰符是ts提出修饰符, */ class People{ type:string constructor(type:string){ this.type = type } } class Police extends People{ name:string constructor(type:string,name:string){ //执行父类的构造器 super(type) this.name = name } } const man = new Police("人类","xiaozhang")
子类继承父类的时候,子类的构造器里面第一句话必须super
访问修饰符。ts提出的一个对属性和行为进行修饰关键字。
private:用于定义我们的属性,这个属性我们可以在奔本类中访问。子类和类的外部都无法访问
public:可以在本类中访问,也可以子类中访问、类的外部访问
protected:受保护的属性买这个属性,本类可以使用,子类可以使用。类的外部不能使用
访问修饰 | 含义 | 范围 |
---|---|---|
private | 私有类型 | 本类 |
protected | 受保护类型 | 本类、子类 |
public | 公有类型 | 本类、子类、外部 |
只读属性,readonly
class Police extends People{ // public代表公共。name属性任何地方都可以使用 public readonly name:string constructor(type:string,name:string){ super(type) this.name = name } show(){ // private设计的私有属性,在子类也调用 console.log(this.type); console.log(this.name); } } const man = new Police("人类","xiaozhang") console.log(man.name); man.name = "xiaofeifei"
比如在React中,我们的props属性,在TS代码中设置的就是只读。只能获取数据,不能修改这个属性
四、类类型接口(扩展)
一个类只能继承另外一个类。不能实现多继承。
但是一个类可以实现多个接口。将无法多继承用接口来解决
// 父类当成规范。 class Phone { call() { console.log("打电话"); } } class Game { play() { } } // 接口的名字,和类的名字是可以重复 interface Phone { // 接口里面定义的一个call方法 call():void } // 在interface里面定义函数,只能由函数的定义,不能有函数实现代码 interface Game { play():void } // 游戏设备 class PSP extends Game { } new PSP().play() // 苹果手机 // implements实现这个接口,也是一种继承,这种继承可以多继承(实现接口) class IPhone implements Phone,Game { call(): void { console.log("这是打电话的功能"); } play(): void { console.log("打游戏"); } }
一个类实现了一个接口,那他必须要在这个中重写接口中的方法
接口中负责定义规范,不负责具体实现,具体实现需要类来完成。
React中Component类
class Component{ render(){ return <div> } } class Login extends Component{ //render方法在父类,定义出来了,并没有实际的业务。 //Component这个类定义好了规范,render方法就是渲染函数 render(){ return ( <div></div> ) } }
通过接口的方式来编程
class Component{ render(){ return <div> } } class Login extends Component{ //render方法在父类,定义出来了,并没有实际的业务。 //Component这个类定义好了规范,render方法就是渲染函数 render(){ return ( <div></div> ) } }
通过Nodejs后端代码连接数据库:
MongoDB
mysql
oracle
Nodejs要连接这些数据库,连接的代码(底层网络通信)不可能让Nodejs来实现。
目前Nodejs16这个版本,可以连接MongoDB、mysql、oracle,如果哪一天市面上多了一个数据库。redis
不可能市面上多出一个数据库,就要求我们nodejs更新一个版本,去连接这个数据库
数据库通信的代码,交给数据库产生来实现。Nodejs要做一个规范,用户在连接数据库的时候,只需要提供几个方法即可
interface DB { connect(url:string):boolean close():void }
MongoDB数据库产商
class MongoDB implements DB{ connect(url:string):void{ //拿着你的url来进行网络通信 return true } close():void }
连接数据库的代码实现了,Nodejs以后开发过程中
1.下载mongodb 2.Nodejs代码连接数据,mongose import {md} from "mongodb" const boo = md.connect("http://127.0.0.1:27017")
mysql这个数据想要跟Nodejs结合起来使用,mysql数据库产生自己实现连接数据库的代码
class Mysql implements DB{ connect(url:string):boolean{ //实现Nodejs代码中连接mysql数据 } }
Nodejs代码中我们要连接mysql
1. 下载依赖 mysql 2. 引入依赖 import {mq} from "mysql" mq.connect()
Vue3-01基本介绍
Vue3-01基本介绍
一、Vue背景
2018年9月份,宣布了vue3.0计划。一直到2020年4月份发布可Vue3.0的第一版本。beta版本
2020年9月正式推出了第一个版本,one piece
目前企业中已经有部分开始使用vue3来进行项目开发。
Vue2的版本采用原始js来开发。
Vue3很多公司都是基于TS来进行开发。Vue3底层全部采用TS来设计。
Vue3和Vue2对比:
-
性能提升:Vue3这个框架将Vue全部重构了。新的框架。底层采用TS来进行重构,性能提升能达到100%
-
对TS的支持,Vue3底层默认采用TS进行开发。我们Vue开发过程中,一般也会默认结合TS来使用
-
Vue3目前创建项目采用了新的打包工具,vite工具(xxx)团队他们自己发布的一个打包。目标干掉webpack
-
新增了组合式api和响应式api进行开发(hook编程)
Vue2的版本不再更新了,目前2.7版本
Vue3还在不断的更新,有些规范api还在完善/
优势:
更快:vue3重写了虚拟dom。提升提升很多
更小:新增了tree shaking技术,你们项目中没有用到的代码。打包会默认去掉。
更易于维护:Flow到TypeScript变化,让我们代码实现起来更加规范
二、搭建Vue3的环境
vite打包工具
Vue2采用 @vue/cli来创建你们的项目,基于webpack来设计的一个脚手架工具
Vue3创建项目采用vite工具,
vite的地址:Vite | 下一代的前端工具链
vite这个打包工具是一种新型的前端构建工具,目的就是提升我们前端的性能。提供开箱即用的环境。以及插件
vite的优势:
项目启动会变得非常的快,相对于webpack封装脚手架来说,效率比较高
代码在需要运行的时候进行编译。只有代码真正在屏幕展示的时候,才会编译
底层热模块的替换更新,代码变更后,热更新会更快速
三、vite来搭建项目
Vite要求Nodejs版本必须12.0以上
(1)使用npm和yarn打开创建项目界面
使用npm来搭建项目
//npm init @vitejs/app npm init vite@latest
使用yarn来创建项目
//yarn create @vitejs/app yarn create vite
(2)选中使用vite来创建那种项目
# 项目名称,默认 vite-project ? Project name: › vite-project # 选择模版:Vue ? Select a framework: › - Use arrow-keys. Return to submit. vanilla ❯ vue react preact lit-element svelte # 选择类型 Vue ? Select a variant: › - Use arrow-keys. Return to submit. ❯ vue vue-ts
vue这个版本,代表默认Vue3+JS
vue-ts这个版本,代表默认Vue3+TS开发
(3)项目创建只需要几秒钟。
项目创建完毕后,只是项目结果完成。但是没有依赖文件
npm install yarn install
下载依赖。你才能启动项目
(4)运行项目
npm run dev
启动项目完成过后页面上会出现Vue3欢迎提示
如果你想要创建项目,直接指定模板。npm版本6.x的时候
npm init vite@latest my-vue-app --template vue
my-vue-app:项目名字
npm的版本是7.x的时候
npm init vite@latest my-vue-app -- --template vue
Vue3开发,我们需要在vscode中安装插件:
Vue3-02项目结构梳理
Vue3-02项目结构梳理
一、项目目录介绍
src目录
——main.ts文件,这个文件整个项目入口文件,里面也包含创建实例,挂载到页面的流程
import { createApp } from 'vue' // 默认给整个项目设置了公共的css样式 import './style.css' // 引入根组件,将App组件挂载到实例上面 import App from './App.vue' // 你最好将代码拆分以下。app这个Vue实例挂载其他插件 // 无需自己在创建Vue实例,createApp封装了 const app = createApp(App) app.mount('#app') /** * Vue2 * new Vue({ * router, * store * }).$mount("#app") */
—-App.vue文件为项目根目录
<template> <div>345</div> <div>333</div> <p>123</p> </template>
在Vue3里面,我们可以支持在template标签下面同时创建多个标签。
Vue3引入了一项技术,类似于React中Fragment,以后再template里面无需再创建根标签,默认采用Fragment来进行页面处理。
<script lang='ts' setup> </script>
这个标签上面,lang=“ts”代表以后再这个script标签里,我们可以支持ts代码。
后续我们打包的时候,就会默认将script标签里面的内容解析为js代码
steup:Vue3采用了新的api来进行编程,这些api想要能够被我们框架识别,必须再script标签上面增加steup标志,才能让我们打包的,将vue3的语法解析渲染
<style lang='scss' scoped> .box { .h3 { color: red; } } </style>
我们在Vue3项目中,创建项目默认不支持less和scss。要使用这两种技术,我们需要自己下来依赖包
二、项目插件和模块配置
开发Vue3,我们可以将Vue2插件关闭了,不然可能影响你们的代码编译
禁用Vetur插件
安装Vue3的插件
配置组件创建的快捷键
步骤如下:
新建一个代码片段,名字vue3
打开你们的片段文件,内容拷贝进去
{ "Print to console": { "prefix": "vue3", "body": [ "<template>", " <div></div>", "</template>", "", "<script lang='ts' setup>", "import { reactive,ref} from 'vue'", "</script>", "", "<style lang='less' scoped>", "</style>" ], "description": "Log output to console" } }
Vue03-基础语法
Vue03-基础语法
一、关于组件的引入
在Vue3这个项目中我们要引入外部的组件,components文件夹组件
特点:
-
引入组件后,我们无需注册,直接页面中渲染
-
默认已经加载src/components文件夹,这个文件夹下面的组件已经可以实现全局导入。
你在组件重要无需引入,默认用了某个组件,直接去src /components寻找
<template> <h3>这是App</h3> <Product></Product> <User></User> </template> <script lang='ts' setup> </script> <style lang='scss' scoped> .box { .h3 { color: red; } } </style>
你以后可以在vue3的配置文件中修改他默认的组件存放目录,src/components更改为其他的文件夹。
二、Fragmenet
在template标签里面 ,我们可以不用在定义根标签,
Vue3默认已经内置了虚拟标签,相当于React中React.fragmenet来进行根标签定义
<template> <div> </div> <div> </div> </template>
三、setup关键字
Vue3提出了组合式api来完成项目开发。这些组合式的api要运行必须要有一个环境。setup就是标志我们当前Vue3的运行环境。
以后只要想用Vue3的组合式api,那就必须将代码放在setup这个模块中。
setup用法有两种:
上一个版本官方的标准语法
<script> export default { setup(){ //vue3的代码写在这个地方 } } </script>
在没有出现新版本之前,官网还提出了另外一种语法。草案中的语法
<script setup> //这里面的代码就可以用组合式api </script>
目前官方最新的语法标准,是第二种语法。
目前企业里面大部分公司都采用第二种语法作为开发标准。
选项式API:就是Vue2这个版本里采用的语法结构。提供了一个对象,对象里包含完整的JS语法、对象
组合式API:目前在Vue3中使用的一种开发模式,按需引入。你需要哪些api就手动引入api,有点像React中hook编程。
四、Vue3中提供组合式api:内部数据
ref基础使用
ref这个组合式api需要我们引入到项目中定义组件内部数据
import {ref} from "vue"
在通过ref来定义内部数据
<template> <div> <h3>Reactive</h3> <p>{{count}}</p> <button @click="count=100">修改</button> <!-- 只要在template输出这个对象,默认调用value --> <p>{{username}}</p> <button @click="changeUsername">修改username</button> </div> </template> <script lang='ts' setup> // 定义组件内部数据 import {ref} from "vue" // 定义一个基本数据类型,useState const count = ref(50) const username = ref("xiaowang") // 函数直接定义script标签里面 const changeUsername = ()=>{ console.log(username); // username得到的不是一个普通数据类型,而是一个对象。 username.value = "xiaofeifei" } </script> <style lang='less' scoped> </style>
script标签里面如果要获取或者修改当前定义的变量,必须对象.value
的方式来实现
但是template标签里面我们可以直接使用ref定义的变量
reactive数据定义
reactive定义数据和ref有点不一样,可以将所有的数据定义为一个对象,方便我们调用
<template> <div> <h3>Reactive</h3> <p>{{state.count}}</p> <p>{{state.user}}</p> <button @click="++state.count">+</button> <button @click="--state.count">-</button> </div> </template> <script lang='ts' setup> import { reactive } from 'vue'; /** * reactive定义组件内部数据 * 类似于React 类组件 State */ const state = reactive({ count:10, user:{ id:1,name:"xiaowang" }, stus:[1,2,3] }) </script> <style lang='less' scoped> </style>
建议:
基本数据类型我们采用ref来定义
引用类型数据我们采用reactive来定义
Reactive来定义的数据,进行修改的时候,不需要value修改,直接进行值变化就可以
五、TS的代码约束
在Vue3中我们采用TS来进行代码约束和规范。
Vue3在设计业务的过程中也采用TS的方式来进行开发。
写代码过程中定义组件内部数据,我们如果没有明确的约束我们数据结构。TS会自动进行类型推断。完成的数据的约束,如果你的数据开始是空的,需要异步请求获取数据然后更新。
提前把约束写好。不要让程序自动类型推断。
<template> <div> <h3>ReactiveData3</h3> <p>{{ state }}</p> <button @click="fecthData">请求</button> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' /** * 针对reactive进行数据约束,以后后端返回的数据如果结构不对 */ interface IUser { id: number, name: string, address: string } interface IState { users:IUser[], array:number[] } // Vue3 proxy let state = reactive<IState>({ users: [{ id: 1, name: "xiaowang", address: "武侯区" }], array:[1,2,3] }) // 覆盖state的值,页面检测不到 const fecthData = () => { state.users = [ { id: 2, name: "xiaowang", address: "高新区" } ] } </script> <style lang='less' scoped> </style>
interface代表接口,可以用reactive泛型这个模块里面。规定了组件内部数据的规范,以后从后端返回到前端的数据,就必须按照这个规范来设计。
在组件中我们为了约束代码,编写interface接口。我们将这些接口单独提取到文件中
src/types文件夹里面创建ReactiveInterface.ts文件
export interface IUser { id: number, name: string, address: string } export interface IState { users:IUser[], array:number[] }
代码中可以直接引入外部的数据
<template> <div> <h3>ReactiveData3</h3> <p>{{ state }}</p> <button @click="fecthData">请求</button> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' import {IUser,IState} from "../../types/ReactiveIterface" /** * 针对reactive进行数据约束,以后后端返回的数据如果结构不对 */ // Vue3 proxy let state = reactive<IState>({ users: [{ id: 1, name: "xiaowang", address: "武侯区" }], array:[1,2,3] }) // 覆盖state的值,页面检测不到 const fecthData = () => { state.users = [ { id: 2, name: "xiaowang", address: "高新区" } ] } </script> <style lang='less' scoped> </style>
ref进行数据约束的代码如下
// count的类型就是number let count = ref<number>(10) let myuser = ref<IUser>({id:1,name:"xiaowang",address:"武侯区"})
Vue3-04基础语法2
Vue3-04基础语法2
在Vue3里实现计算属性的操作。
一、计算属性
Vue2中计算属性
export default { computed:{ fullName(){ return this.firstName + this.lastName } } }
Vue3提供的所有操作都是采用组合式api的方式呈现的。
我们如果要实现计算属性,也需要导入计算属性的模块,来进行计算属性操作
<template> <div> <h3> computed </h3> <p>{{fullName}}</p> <button @click="state.firstName='老'">修改姓</button> </div> </template> <script lang='ts' setup> import { reactive,ref,computed} from 'vue' const state = reactive({ firstName:"小", lastName:"王" }) const fullName = computed(()=>{ return state.firstName + state.lastName }) </script> <style lang='less' scoped> </style>
计算属性也被封装为组合式的api,我们需要引入这个api来进行计算属性业务。
computed被封装成了一个函数,需要计算属性的时候,调用这个函数获取结果
这种编程的方式更加偏向hook编程
语法2:
<template> <div> <h3> computed </h3> <p>{{fullName}}</p> <p>{{upperCaseName}}</p> <p>{{newValue}}</p> <button @click="newValue='zhang'">修改newValue</button> <button @click="state.firstName='老'">修改姓</button> </div> </template> <script lang='ts' setup> import { reactive,ref,computed} from 'vue' const state = reactive({ firstName:"小", lastName:"wang" }) const newValue = computed({ get(){ console.log(123); return state.lastName }, set(val){ console.log(val); } }) </script> <style lang='less' scoped> </style>
语法2相对来说使用比较少,计算属性的值,一般获取来使用,很少对进行修改
二、watch模块
在Vue3.0里面watch也是通过api的方式来提供的,功能跟之前vue2的侦听器一样的效果
Vue2的语法
export default { data(){ return { username:"xiaowang" } }, watch:{ username:{ //当我们监控的username发生变化的时候,默认执行handler handler(value){ }, deep:true, immediate:true } } }
Vue3里面watch也是采用api的形式提供。
<template> <div> <h3>watch模块</h3> <p>原始数据:{{state.username}}</p> <p>{{state.user}}</p> <button @click="state.username = 'xiaofeifei'">修改username</button> <button @click="state.user.name = 'xiaoliu'">修改user</button> </div> </template> <script lang='ts' setup> import { reactive,ref,watch} from 'vue' const state = reactive({ username:"xiaowang", user:{ id:1,name:"王二麻子" } }) // 监听watch模块 // 第一个回调函数,要监控的属性是哪些。 // 第二个回调函数,数据发生变化,要执行业务 watch( ()=>state.username, (val,prevVal)=>{ console.log(val); console.log(prevVal); } ) </script> <style lang='less' scoped> </style>
如果我们要监控user对象
当我们要监控引用类型的数据。需要深度监听和立即监听
第三个参数:提供一个对象,对象里加入deep:true、immediate:true
watch( () => state.user, (val, prevVal) => { console.log(val); console.log(prevVal); }, { deep:true, immediate:true } )
三、watchEffect函数
响应式的在跟踪你们的依赖项,一旦检测变化,立即执行回调函数。
watchEffect和watch都是用于进行页面的监控。
watchEffect提供了一个回调函数,这个函数中使用reactive中某些属性,只要这些属性发生变化,我们都可以监控到
watchEffect(()=>{ // 里面可以执行异步请求,日志记录等等 console.log(state.username); // 监控user这个对象地址发生变化,里面属性变化检测 console.log(state.user); // 使用了user对象里面某个属性才能监控到 console.log(state.user.name); })
wactEffect里面使用到的变量发生变化,我们才能监控到。
如果引用类型数据,地址发生变化你才能监控到。除非指定要监控引用类型里面某个属性
watchEffect一次性可以监控很多个属性。
四、声明周期函数
在VUE2中我们声明周期函数可以直接获取使用。
export default { mounted(){ }, created(){ } }
Vue3中所有声明周期函数都需要引入后使用
<template> <div> <h3> 声明周期函数 </h3> </div> </template> <script lang='ts' setup> import { reactive,ref,onMounted} from 'vue' onMounted(()=>{ console.log("页面挂载完成后执行"); }) </script> <style lang='less' scoped> </style>
采用api的进入引入,调用声明周期函数执行
对比选项式API和组合式API
下面表格就是将选项式API和Vue组合式api函数区别:
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed |
created | Not needed |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmount |
destory | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
以后大家在使用这些生命周期函数的时候,需要按需引入使用
抽奖练习: https://www.17sucai.com/pins/demo-show?id=33475&st=Ji6D80pE4fsP8FtbjfiX6A&e=1661245538
要求:
1.每次中奖的两个数据不能重复。
2.中奖名单出来后(中奖名单的电话号码 隐藏中间4位),请给每个中奖名单加上点击事件,点击后标注为红色边框。并将他的电话完整的显示出来。
<template> <div> <h2>抽奖作业</h2> <h3>中奖名单</h3> <ul> <li @click="choosePrize(item.id)" :class="{ as: item.check }" v-for="item in prizeUser.users" :key="item.id"> <!-- 名字 --> <span>{{ item.name }}</span> <!-- 电话 --> <span v-if="item.check">{{ item.phone }}</span> <span v-else>{{ filter(item.phone) }}</span> </li> </ul> <button @click="begin">{{ status == -1 ? "开始" : "暂停" }}</button> <Button type="primary">antd</Button> <a-button type="primary">Primary Button</a-button> <a-button>Default Button</a-button> <a-button type="dashed">Dashed Button</a-button> <a-button type="text">Text Button</a-button> <a-button type="link">Link Button</a-button> <div class="wrapper"> <span>蜗牛</span> </div> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' import { IState } from "../../types/PrizeInterface" import { Button } from 'ant-design-vue'; const state = reactive<IState>({ users: [ { id: 1, name: "xiaowang", phone: "12354545", check: false }, { id: 2, name: "xiaozhang", phone: "12354545", check: false }, { id: 3, name: "xiaomei", phone: "12354545", check: false }, { id: 4, name: "xiaofei", phone: "12354545", check: false }, { id: 5, name: "xiaoliu", phone: "12354545", check: false }, { id: 6, name: "xiaowu", phone: "12354545", check: false }, { id: 7, name: "xiaoqiang", phone: "12354545", check: false }, { id: 8, name: "xiaoyi", phone: "12354545", check: false }, ] }) const prizeUser = reactive<IState>({ users: [] }) // timmer默认值 const status = ref(-1) /** * 第一次进来,先判断定时器是否为-1.没有开启定时器 */ const begin = () => { if (status.value == -1) { status.value = setInterval(() => { getPrizeValue() }, 500) } else { clearInterval(status.value) status.value = -1 } // console.log(123); } const getPrizeValue = () => { const { users } = state const newArray = [] while (newArray.length < 4) { // 构造每次中奖 let index = parseInt(Math.random() * users.length + "") // 判断不存在 if (newArray.indexOf(users[index]) == -1) { newArray.push(users[index]) } } prizeUser.users = newArray } // Vue中目前没有提供过滤器 const filter = (val: string) => { return val.replace(val.substring(3, 7), "***") } const choosePrize = (id: number) => { const item = prizeUser.users.find(item => item.id == id) // 检测你对undefined进行处理 if (item) { item.check = !item.check } } </script> <style lang='scss' scoped> .as { width: 200px; height: 25px; border: 1px solid red; } .wrapper{ span{ color: tomato; } } </style>
评价练习:
<template> <div> <!-- 动态渲染小星星 --> <ul class="oul"> <li class="star" v-for="(item,index) in state.stars" :key="item.id" @click="changeStar(index)" > <img v-if="item.type==1" src="../../assets/images/x1.png" alt=""> <img v-else src="../../assets/images/x2.png" alt=""> </li> </ul> <p>{{state.chooseText}}</p> <!-- 动态进行遍历我们评价模块 --> <div class="box" :class="{checked:item.check}" v-for="item in state.comments" :key="item.id" @click="changeState(item.id)" > {{item.name}} </div> <textarea v-model="state.title" name="" id="" cols="30" rows="10"></textarea> <span>还剩{{state.totalText}}个字</span> </div> </template> <script lang='ts' setup> import { reactive, ref,watchEffect } from 'vue' // 评价的数据 const state = reactive({ stars: [ { id: 1, text: "极差", type: 1 }, //灰色1 type:2 { id: 2, text: "较差", type: 1 }, { id: 3, text: "中等", type: 1 }, { id: 4, text: "一般", type: 1 }, { id: 5, text: "好评", type: 1 }, ], chooseText:"请选中", comments:[ {id:1,name:"准时交付",check:false}, {id:2,name:"效果明显",check:false}, {id:3,name:"使用恰当",check:false}, {id:4,name:"质量保证",check:false}, ], totalText:140, title:"" }) watchEffect(()=>{ state.totalText=140 - state.title.length }) const changeStar = (index:number)=>{ // 进入函数,默认将所有的type都设置为1 state.stars.forEach(item=>item.type=1) state.stars.slice(0,index + 1).forEach(item=>{ item.type = 2 }) state.chooseText = state.stars[index].text } const changeState = (id:number)=>{ const item = state.comments.find(item=>item.id==id) if(item){ item.check = !item.check } } </script> <style lang='less' scoped> .oul { list-style: none; display: flex; .star { width: 24px; height: 24px; } } .box{ width: 130px; height: 25px; border: 1px solid gray; text-align: center; line-height: 25px; &.checked{ border: 1px solid tomato; } } </style>
Vue3-05其他环境搭建
Vue3-05其他环境搭建
一、antd组件库
Vue2一般会结合ElementUI来使用。
Vue3一般会使用antd、或者element PLUS(TS支持)
antd提供了一个ant design vue这个版本。针对vue来使用的组件库
https://www.antdv.com/components/overview-cn
(1)下载依赖
yarn add ant-design-vue
(2)在我们main.ts文件中引入样式
import 'ant-design-vue/dist/antd.css'; // or 'ant-design-vue/dist/antd.less'
(3)在对应的组件中引入antd的模块
import { Button } from 'ant-design-vue'; <template> <Button type="primary">antd</Button> </template>
(4)无需引入,直接动态加载
<template> <div> <button @click="begin">{{ status == -1 ? "开始" : "暂停" }}</button> <Button type="primary">antd</Button> <a-button type="primary">Primary Button</a-button> <a-button>Default Button</a-button> <a-button type="dashed">Dashed Button</a-button> <a-button type="text">Text Button</a-button> <a-button type="link">Link Button</a-button> </div> </template>
(5) 按需加载
打包的时候,你用到哪些组件,我们打包对应css样式
下载插件
npm i unplugin-vue-components -save-dev
这个插件只能在开发过程中用到,打包过后就没有用,这个插件开发依赖中
vite.config.js文件中配置插件
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite'; import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), Components({ resolvers: [AntDesignVueResolver()], }), ] })
你们就可以在main.ts中删除样式引入
import { createApp } from 'vue' import App from './App.vue' // import 'ant-design-vue/dist/antd.css'; const app = createApp(App)
(6)配置全局引入组件。无需手动引入
在Vue3里,你可以配置你的组件全局加载,任何一个地方直接使用。import引入也可以使用
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Components from 'unplugin-vue-components/vite'; import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), //默认的vue保留 Components({ dirs: ['src/components'], resolvers: [AntDesignVueResolver()], }), ] })
dirs这个配置代表默认要加载的组件地址。
components
文件夹下面所有的组件,默认去加载。在页面中引入使用。
二、配置less插件
Vue3里面默认没有支持less的插件,如果要使用less,我们只需要下载依赖包。
无需配置,默认加载依赖包
npm i less-loader less --save-dev
三、配置scss插件
scss插件需要下载三个依赖,但是下载antd过后,scss有些依赖已经被antd下载完毕,
你们要Vue3中使用scss,只需要sass插件就可以实现效果
npm install sass --save-dev
Vue3-06组件通信
Vue3-06组件通信
Vue3中父子组件通信和我们之前Vue2是同样的规则。
Vue3引入了TS来开发,通信在TS约束下面,有一些语法不一样。
父组件传递值
<template> <div> <h2>父组件</h2> <p>{{count}}</p> <button @click="count=20">修改count</button> <ChildComp :count={count}></ChildComp> </div> </template> <script lang='ts' setup> import { reactive,ref} from 'vue' const count = ref<number>(10) </script> <style lang='less' scoped> </style>
在组件上传递动态数据给子组件。
子组件获取父组件传递过来的数据
<template> <div> <h3>子组件</h3> <p>{{count}}</p> <p>{{username}}</p> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' // 使用Vue3内置的一个api来获取外部传递进来的数据 defineProps < {count: number,username:string} > () </script> <style lang='less' scoped> </style>
子组件defineProps来接受外部的数据。外部数据传递进来。可以用泛型来定义外部变量,接受这个数据
defineProps这个函数需要引入,可以直接调用。
父组件传递了自定义事件给子组件
<template> <div> <h3>子组件</h3> <p>{{count}}</p> <p>{{username}}</p> <button @click="changeCountChild">修改count</button> <button @click="changeUsernameChild">修改username</button> </div> </template> <script lang='ts' setup> import { reactive, ref } from 'vue' // 使用Vue3内置的一个api来获取外部传递进来的数据 defineProps < {count: number,username:string} > () // 需要使用Vue3里另外一个api来获取自定义事件函数 // 约束外部传递进来的函数,changeCount,这个函数有一个参数val const emit = defineEmits<{ (e:"changeCount",val:number):void, (e:"changeUsername",val:string):void }>() const changeCountChild = ()=>{ emit("changeCount",100) } const changeUsernameChild = ()=>{ emit("changeUsername","xiaofeifei") } </script> <style lang='less' scoped> </style>
definedProps可以用于接受组件外部的数据。在TS版本中,我们是通过泛型来获取外部的值
definedEmits这个函数可以定义组件访问自定义事件函数,emit对象触发函数,数据回传给父组件
Vue3-07路由搭建
Vue3-07路由搭建
Vue3在创建项目的时候,并没有搭建好路由环境。
路由需要我们自己手动搭建出来
将路由环境搭建出来。在引入TS来进行路由的约束
一、路由环境搭建
(1)下载路由依赖
npm i vue-router@next
@next代表目前最新的版本
(2)在src/routes/index.ts文件,设计路由映射关系
import {createRouter,createWebHashHistory,createWebHistory} from "vue-router" import Home from "../views/Home.vue" const routes = [ { path:"/home", name:"HOME", component:Home } ] const router = createRouter({ routes, // createWebHashHistory()代表路由hash模式。 // createWebHistory() 代表history模式 history:createWebHistory() }) export default router // Vue2代码 // const router = new VueRouter({ // router, // mode:"hash" //"history" // })
(3)将routes/index文件引入到我们main.ts加载
import { createApp } from 'vue' // 默认给整个项目设置了公共的css样式 // import './style.css' // 引入根组件,将App组件挂载到实例上面 import App from './App.vue' // import 'ant-design-vue/dist/antd.css'; import router from "./routes" // 你最好将代码拆分以下。app这个Vue实例挂载其他插件 // 无需自己在创建Vue实例,createApp封装了 const app = createApp(App) // 挂载router,启动项目加载router,页面中获取router进行参数获取跳转 app.use(router) app.mount('#app')
一定要注意,先执行app.use(router)再来执行app.mount()
(4)引入vue-router官方提供的约束,对映射关系进行内容约束
import {createRouter,createWebHashHistory,createWebHistory,RouteRecordRaw} from "vue-router" import {Component} from "vue" import Home from "../views/Home.vue" // routes进行ts约束, // routes路由目前默认是写死的,如果以后路由需要动态加载,后端将路由返回前端 // addRouter() [{},{},{}] const routes:Array<RouteRecordRaw> = [ { path:"/home", name:"HOME", component:Home, props:true } ] const router = createRouter({ routes, // createWebHashHistory()代表路由hash模式。 // createWebHistory() 代表history模式 history:createWebHistory() }) export default router
路由的约束,你们可以自己实现IRouter,但是内容比较多,约束写起来有点麻烦
// interface IRouter { // path:string, // name:string, // component:Component, // meta?:{}, // beforeEnter: (to:{}, from:{}, next:object) => {} // }
最后,在App.vue中添加渲染出口即可
<script setup lang="ts"> </script> <template> <!-- 路由渲染出口 --> <router-view></router-view> </template> <style scoped> </style>
Vue3-08Vuex状态机搭建
Vue3-08Vuex状态机搭建
Vue3创建项目的时候,并没有搭建状态机环境。我们需要再项目中自己引入状态机
并且配合TS来进行约束
一、搭建流程
(1)下载状态机的依赖
npm i vuex@next
(2)src目录下面创建store / index.ts
store里面index.ts文件,
modules文件夹,里面存放子仓库
index.ts文件中我们要设计仓库的代码
import {createStore} from "vuex" import {IRootState} from "../types/MainStore" const store = createStore<IRootState>({ state:{ username:"xiaowang", users:[ {id:1,name:"xiaofeiei"} ], count:10 }, getters:{ }, mutations:{ increment(state,payload){ state.count += payload }, decrement(state,payload){ state.count -= payload } }, actions:{ }, modules:{ } }) export default store
需要引入TRootState约束
export interface IUser { id:number, name:string } export interface IRootState { username:string, users:IUser[], count:number }
(3)再main.ts文件中加载我们store仓库
import { createApp } from 'vue' import App from './App.vue' import router from "./routes" import store from "./store" const app = createApp(App) app.use(router) app.use(store) //一定先use加载插件,mount来挂载到页面 app.mount('#app')
(4)再页面中引入store仓库来操作仓库
<template> <div> <h3>用户页面</h3> <p>{{store.state.count}}</p> <button @click="increment">-</button> <button @click="decrement">+</button> </div> </template> <script lang='ts' setup> import { reactive,ref} from 'vue' import {useStore} from "vuex" // 调用useStorehook获取仓库对象 const store = useStore() const increment = ()=>{ // 调用仓库的mutations方法 commit store.commit("increment",10) } const decrement = ()=>{ store.commit("decrement",5) } </script> <style lang='less' scoped> </style>
在Vue3里我们操作仓库,要使用vuex提供的hook函数来操作useStore
this.$store mapState\mapMuations 都无法在Vue3中使用
(5)异步更新仓库数据
actions:{ asyncIncrement(context,payload){ setTimeout(() => { context.commit('increment',payload) }, 1000); } },
页面上
const asincrement = ()=>{ store.dispatch("asyncIncrement",3) }
二、搭建子仓库
我们需要主仓库的models中引入外部模块
models文件下面创建product.ts
import { Module } from "vuex" // Module 这个模块专门提供用于约束子模块 // 编写一个接口,约束state interface IUser { username:string } interface IRoot { } // 两个泛型:约束子仓库state,ROOT主仓库完整约束 const product:Module<IUser,IRoot> = { namespaced:true, state: { username:"xiaowang", }, getters: {}, mutations: { changeUsername(state,payload){ state.username = payload } }, actions: { asyncChangeUsername(context,payload){ setTimeout(() => { context.commit("changeUsername",payload) }, 1000); } }, } export default product
在子仓库中要约束数据,需要Module模块。里面对子仓库的数据进行约束
在主仓库引入子仓库
import {createStore} from "vuex" import productModel from "./modules/product" const store = createStore<IRootState>({ modules:{ productModel } }) export default store
页面上执行渲染和修改
<p>{{store.state.productModel.username}}</p> const changeSubStore = ()=>{ store.commit("productModel/changeUsername","华仔") } const aschangeSubStore = ()=>{ store.dispatch("productModel/asyncChangeUsername","雇佣者") }
要访问子仓库,我们必须指定命令空间名字
umijs01-基本概念
umijs01-基本概念
一、概念
umijs是React体系的框架。学习umijs就在学习React编程。
umijs蚂蚁金服内部设计,并开源出来的一个React封装框架。
umijs将React框架又进了封装,常用的插件已经全部集成到我们的框架当中。
umijs就是一个开箱即用的React框架。
React项目开发,比较难的一点环境搭建,需要开发者下载很多依赖包。umijs已经将我们常用的插件配置完毕了,你只需要打开项目就能配置使用。
umijs(乌米),在蚂蚁的体系下面用的最多。外面中小型公司如果要涉及到React开发,考虑用umijs来进行研发。
开发文档:快速上手
二、项目创建
(1)先在本地创建项目名字
你可以手动创建,你也可以使用下面命令行
mkdir myapp
cd myapp
(2)执行创建
yarn create @umijs/umi-app # 或 npx @umijs/create-umi-app
(3)下载依赖
yarn install
(4)启动项目
yarn start
三、umijs使用less
umijs内置了less开发,我们无需再配置less环境,直接使用就行
React一般配合less来开发,没有配置scss环境,如果要使用scss,你需要自己配置scss环境
import styles from './index.less'; export default function IndexPage() { return ( <div> <h1 className={styles.title}>Page index</h1> <div className={styles.wrapper}> <span className={styles.sp}>蜗牛</span> </div> </div> ); }
四、umijs内置antd
我们想要再umijs中使用antd,无需配置,直接使用就可以了。
umijs目前这个版本,内置了antd4.x版本。
而且umi已经实现按需加载,无需配置任何内容,你引入的组件,打包的时候默认将样式打包进去
import styles from './index.less'; import {Button} from "antd" export default function IndexPage() { return ( <div> <Button type='default'>按钮</Button> <Button type='primary'>按钮</Button> <Button type='dashed'>按钮</Button> </div> ); }
如果要配置主题色,umijs也已经配置完成,我们只需要指定颜色就可以了
umirc.ts文件是我umijs非常重要的配置文件,几乎所有配置项我们以后都是再这个文件中操作
import { defineConfig } from 'umi'; export default defineConfig({ nodeModulesTransform: { type: 'none', }, routes: [ { path: '/', component: '@/pages/index' }, ], fastRefresh: {}, //配置antd的主题色 theme: { 'primary-color': '#ffa39e' }, });
umijs02-路由搭建
umijs02-路由搭建
一、配置
我们再umi中需要配置文件有两类:
config/config.ts文件中可以配置
umirc.ts文件也可以配置,优先级会更高。
学习过程中,配置放在umirc.ts 文件
二、路由搭建流程
配置式路由——>Vuejs这种设计靠齐。
路由的配置信息需要在umitc.ts文件中进行配置。
routes: [ { path: '/', component: '@/pages/index' }, { path: '/login', component: '@/pages/login/Login' }, { path: '/register', component: '@/pages/register/Register' }, { path: '/home', component: '@/pages/home/Home', routes: [ { exact: true, path: '/home', redirect: '/home/product' }, { path: '/home/product', component: '@/pages/product/Product' }, { path: '/home/user', component: '@/pages/user/User' }, ], }, ],
特点:
-
无需引入组件,直接component后面写上组件地址,默认加载
-
一级路由配置,无需设置路由渲染出口,默认一进来加载的就是一级路由
-
二级路由配置,需要手动指定路由渲染出口。
{props.children}
-
我们也可以进行路径重定向
{ exact: true, path: '/home', redirect: '/home/product' },
exact:代表设置路由是否精确匹配。umijs的路由中,默认都是精确匹配
-
当umi发现你有二级路由配置时候,外层路由匹配默认模糊匹配。这样设计的目的就是让我们路由能够进入到二级路由甚至更多嵌套路由
三、路由跳转
我们要进行路由跳转,需要引入跳转组件,默认umi引入组件
import React from 'react' import { Link,NavLink } from "umi" import {useHistory} from "umi" export default function Product(props:any) { const history = useHistory() const gotoPage = ()=>{ // props.history.push("/home/user") history.push("/home/user") } return ( <> <div>Product</div> <div> <Link to="/home/user">用户界面</Link> <button onClick={gotoPage}>跳转</button> </div> </> ) }
跳转404的情况
routes: [ { path: '/', component: '@/pages/index' }, ... { component: '@/pages/404' }, ],
当我们页面跳转过后,无法匹配路由的时候,你们可以在一级路由下面配置一个404。pages目录下面创建404的页面。
四、路由权限
访问这个路由的时候,到底能够访问取决于我们是否有权限,或者是否认证通过。
如果没有权限,无法访问这个路由
React开发过程中为了解决这个问题,自己封装了一个高阶组件。
路由提取出来,进行动态匹配。动态生成Route组件
umijs中完成对路由权限的设计,我们只需要配置就完成
routes: [ { path: '/', component: '@/pages/index' }, { path: '/login', component: '@/pages/login/Login' }, { path: '/register', component: '@/pages/register/Register', wrappers: [ '@/wrappers/auth' ], }, { path: '/home', component: '@/pages/home/Home', routes: [ { exact: true, path: '/home', redirect: '/home/product' }, { path: '/home/product', component: '@/pages/product/Product' }, { path: '/home/user', component: '@/pages/user/User' }, ], }, { component: '@/pages/404' }, ],
在你指定的路由上面添加wrappers高阶组件。进行身份认证。
你自己要提供auth组件来进行逻辑处理。
auth组件代码为
import { Redirect } from 'umi' export default (props: any) => { // useAuth属于用户自定义hook。 // const { isLogin } = useAuth(); const isLogin = false if (isLogin) { // props.children渲染你访问组件 return <div>{props.children}</div>; } else { // 跳转到登录页面 return <Redirect to="/login" />; } }
五、路由参数传递
参数传递跟React中是一样的效果
// history.push("/home/user?id=1") history.push({ pathname:"/home/user", query:{ id:1 } })
路径拼接和传递对象我们可以将参数传递给另外一个页面
接受参数
import React, { useEffect } from 'react' import { useLocation, useParams } from "umi" export default function User(props: any) { const location = useLocation() const params = useParams() useEffect(() => { console.log(location); console.log(params); console.log(props); }, []) return ( <div>User</div> ) }
umi参数传递,不管是那种方式,默认已经封装为一个对象,直接获取使用
umijs03-约定式路由
umijs03-约定式路由
一、约定式路由概念
约定式路由时umi提供的一种路由方式,无需自己搭建路由映射环境。
文件系统就是你的路由系统。按照官方给定的约束,来创建项目结构。
路由会自动生成。
当你们umirc.ts 文件中的routes配置删除的时候,那我们约定式路由就生效了。
二、配置项
首先启动umi约定式路由,我们umirc中routes配置删除
文件系统就是你的路由系统
参考:约定式路由
比如以下文件结构:
. └── pages ├── index.tsx └── users.tsx
会得到以下路由配置,
[ { exact: true, path: '/', component: '@/pages/index' }, { exact: true, path: '/users', component: '@/pages/users' }, ]
需要注意的是,满足以下任意规则的文件不会被注册为路由,
-
以
.
或_
开头的文件或目录 -
以
d.ts
结尾的类型定义文件 -
以
test.ts
、spec.ts
、e2e.ts
结尾的测试文件(适用于.js
、.jsx
和.tsx
文件) -
components
和component
目录 -
utils
和util
目录 -
不是
.js
、.jsx
、.ts
或.tsx
文件 -
文件内容不包含 JSX 元素
嵌套路由
Umi 里约定目录下有 _layout.tsx
时会生成嵌套路由,以 _layout.tsx
为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 props.children
渲染子组件。
比如以下目录结构,
. └── pages └── users ├── _layout.tsx ├── index.tsx └── list.tsx
会生成路由,
[ { exact: false, path: '/users', component: '@/pages/users/_layout', routes: [ { exact: true, path: '/users', component: '@/pages/users/index' }, { exact: true, path: '/users/list', component: '@/pages/users/list' }, ] } ]
全局 layout
约定 src/layouts/index.tsx
为全局路由。返回一个 React 组件,并通过 props.children
渲染子组件。
比如以下目录结构,
. └── src ├── layouts │ └── index.tsx └── pages ├── index.tsx └── users.tsx
会生成路由,
[ { exact: false, path: '/', component: '@/layouts/index', routes: [ { exact: true, path: '/', component: '@/pages/index' }, { exact: true, path: '/users', component: '@/pages/users' }, ], }, ]
一个自定义的全局 layout
如下:
import { IRouteComponentProps } from 'umi' export default function Layout({ children, location, route, history, match }: IRouteComponentProps) { return children }
不同的全局 layout
你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx
中对 location.path
做区分,渲染不同的 layout 。
比如想要针对 /login
输出简单布局,
export default function(props) { if (props.location.pathname === '/login') { return <SimpleLayout>{ props.children }</SimpleLayout> } return ( <> <Header /> { props.children } <Footer /> </> ); }
404 路由
约定 src/pages/404.tsx
为 404 页面,需返回 React 组件。
比如以下目录结构,
. └── pages ├── 404.tsx ├── index.tsx └── users.tsx
会生成路由,
[ { exact: true, path: '/', component: '@/pages/index' }, { exact: true, path: '/users', component: '@/pages/users' }, { component: '@/pages/404' }, ]
这样,如果访问 /foo
,/
和 /users
都不能匹配,会 fallback 到 404 路由,通过 src/pages/404.tsx
进行渲染。
权限路由
通过指定高阶组件 wrappers
达成效果。
如下,src/pages/user
:
import React from 'react' function User() { return <>user profile</> } User.wrappers = ['@/wrappers/auth'] export default User
然后在 src/wrappers/auth
中,
import { Redirect } from 'umi' export default (props) => { const { isLogin } = useAuth(); if (isLogin) { return <div>{ props.children }</div>; } else { return <Redirect to="/login" />; } }
这样,访问 /user
,就通过 useAuth
做权限校验,如果通过,渲染 src/pages/user
,否则跳转到 /login
,由 src/pages/login
进行渲染。
umijs04-网络请求
umi04-网络请求
umi框架内置了网络请求umi-request
我们在开发过程中可以使用内置的请求模块,也可以使用axios
内置umi-request模块
import React from 'react' import request from "umi-request" export default function Index() { const fetchData = () => { request.get("http://127.0.0.1:8002/users/getAccountList2") .then(res=>{ console.log(res); }) .catch(error=>{ console.log(error); }) } return ( <div> <h3>图表统计</h3> <button onClick={fetchData}>请求发送</button> </div> ) }
umi-request是内置的网络请求,底层采用的fetch这项技术来实现请求发送。
request.post("地址",{data:{}})
axios来发送请求
内置的umi-request可以实现发送请求,fetch请求功能模块相对axios来说,没有那么丰富。比如请求拦截器、终端请求等等都没有axios这么完善,一般还是使用axios来处理请求
下载依赖
yarn add axios
示例代码
import React from 'react' import request from "umi-request" import axios from 'axios' export default function Index() { const fetchData = () => { request.get("http://127.0.0.1:8002/users/getAccountList2") .then(res=>{ console.log(res); }) .catch(error=>{ console.log(error); }) } const axiosGetData = ()=>{ axios.get("http://127.0.0.1:8002/users/getAccountList2").then(res=>{ console.log(res); }) } return ( <div> <h3>图表统计</h3> <button onClick={fetchData}>请求发送</button> <button onClick={axiosGetData}>axios请求</button> </div> ) }
umijs05-dva数据管理
umijs05-dva数据管理
一、状态管理概念
react这个框架一般会结合redux来完成数据仓库管理
原生的react开发,react+redux+redux-thunk中间件来完成项目开发
在umi中默认没有自己封装redux状态机。
redux发送异步请求:
-
redux-thunk
-
redux-saga
有一个框架dva,这个框架也是react封装框架。dva框架主要封装的react-redux-redux-saga。
一般在开发过程中很少会直接使用dva框架。
umijs这个框架把dva这个框架整合起来,umi框架内置了dva。就会将状态管理交给dva来进行管理。
在umi框架中,dva其实就是作为一个插件,可以在项目中引入进来。我们就可以实现状态管理。
dva框架:DvaJS
二、搭建状态机
我们无需下载任何插件,umi框架已经整合了所有的插件和依赖
按照约定的规范来开发程序,就能实现状态管理
(1)在项目src目录下面创建文件夹models
只要是放在models文件夹下面ts文件,默认就会被加载为状态的文件
在models文件夹下面UserModel模块
/** * 这个userModel专门用于管理user的数据 */ export default { // user模块的状态机数据 state:{ count:10 }, // 处理异步请求 effects:{ }, // 存放我们reducer代码 reducers:{ // reducers里面每个函数其实就是以前case initState(state:any,action:any){ state.count = action.payload return state } } }
(2)页面上使用状态机的数据
import {useDispatch,useSelector} from "umi"
使用hook函数来获取状态机的数据
const {count} = useSelector(state=>{ console.log(state); return state.userModel }) const dispatch = useDispatch() <p>{{count}}</p>
从状态机获取的数据,默认文件名字就是命名空间名字。必须要指定命名空间
(3)派发请求修改仓库数据
const changeCount = ()=>{ // 派发action到reducer的时候,必须指定命名空间名字 dispatch({type:"userModel/initState",payload:100}) }
需要获取到action对象,这个action对象里面需要提供命名空间,才能找到对应的reducers函数
在页面上ts检测useSelector无法加载模块
// 加上类型约束 type RootState = {userModel:{count:number}} const {count} = useSelector((state:RootState)=>{ console.log(state); return state.userModel })
三、完成异步请求
redux默认没有异步请求处理的模块,我们需要借助于中间件来实现状态管理
redux-thunk:加强dispatch,让我们在派发到reducer之前发送异步请求,得到结果复制reducer
redux-saga:也是用于处理异步请求的中间件,采用与generator编程来实现异步请求
面试题:generator异步请求解决方案。iterator迭代器。
在我们userModel模块中我们可以使用effects来定义异步请求
/** * 这个userModel专门用于管理user的数据 */ import { getAllUser } from "../apis/user" export default { // user模块的状态机数据 state: { count: 10, users:[] }, // 处理异步请求 effects: { // 名字无所谓redux-saga // 这个语法是generator代码 *getAllUserSaga({payload},{call,put}){ // 发送异步请求(这里的payload就是页面传过来的参数) console.log(payload); // 默认只能传递一个参数,如果多个参数,封装为对象传递过去 const res = yield call(getAllUser,payload) console.log(res); // 继续将结果派发给reducer更新数据put就是在执行dispacth // 等待结果,put方法底层默认调用disptach派发请求 yield put({type:"initUser",payload:res.data.data}) } }, // 存放我们reducer代码 reducers: { initUser(state:any,action:any){ state.users = action.payload return { ...state } } } }
页面上调用:
import React from 'react'; import { useDispatch, useSelector } from 'umi'; export default function index() { // 定义数据类型 type RootState = { userModel: { count: number,users:[] } }; // 引入userModel的数据 const { count,users } = useSelector((state: RootState) => { return state.userModel; }); // 引入dispatch const dispatch = useDispatch(); // 异步获取user的数据 const asGetData = () => { dispatch({ type: 'userModel/getUserSage',payload:{id:"1"} }); }; return ( <div> <h2>图标统计</h2> <p>{users.length}</p> <button onClick={asGetData}>异步请求</button> </div> ); }
effect这个这个模块里面,我们需要编写generator代码,
里面每个函数前面都需要加上*,
在函数有里面yield关键字来控制执行顺序。等待异步的结果
put方法来进行dispatch调用,更新仓库数据
umijs06-组件内部数据约束
umijs06-组件内部数据约束
useState可以定义组件的内部数据,我们可以使用type和interface来进行约束
一、state内部数据约束
函数数组约束:
import React,{useState} from 'react' import {IUser,IProduct} from "../../types/user" export default function List() { // 定义我们count数据的时候,默认的数据类型为number类型 const [count,setCount] = useState<number>(10) const [user,setUser] = useState<IUser>({id:1,name:"xiaowang"}) const [product,setProduct] = useState<IProduct[]>([ {id:1,name:"小米"} ]) return ( <div> <p>{count}</p> <button onClick={()=>setCount(100)}>修改</button> </div> ) }
类组件约束:
import React, { Component } from 'react' interface IProduct { id:number, name:string } interface IState { list:IProduct[] } interface IProps { } export default class Cart extends Component { // state数据已经被约束了 state:IState={ list:[ {id:1,name:"小米"} ] } render() { return ( <div>Cart</div> ) } }
二、props外部数据约束
函数组件
创建一个子组件,这个子组件要接受外部传递进来的参数
import React,{ReactNode} from 'react' interface IProps { count:number, // ReactNode代表React节点类型 children?:ReactNode } export default function Header(props:IProps) { return ( <div> <h4>Header</h4> <p>{props.count}</p> {/* <p>{props}</p> */} </div> ) }
父组件要传递参数给子组件
import React,{useState} from 'react' import {IUser,IProduct} from "../../types/user" import Header from '@/components/Header' export default function List() { // 定义我们count数据的时候,默认的数据类型为number类型 const [count,setCount] = useState<number>(10) return ( <div> <Header count={count}></Header> </div> ) }
如果子组件那边要求必须传递参数,父组件按照子组件的标准来传递参数。
子组件那边约束Props里面函数类型
import React,{ReactNode} from 'react' interface IProps { count:number, // ReactNode代表React节点类型 user:{ id:number, name:string }, changeCount(val:number):void, children?:ReactNode } export default function Header(props:IProps) { return ( <div> <h4>Header</h4> <p>{props.count}</p> <button onClick={()=>props.changeCount(1000)}>修改count</button> {/* <p>{props}</p> */} </div> ) }
类组件
要约束外部props
import React, { Component } from 'react' interface IProps { count:number, changeCount(val:number):void } interface IState { } // 第一个泛型用于约束props对象,第二个泛型state export default class Footer extends Component<IProps,IState> { render() { const {count,changeCount} = this.props return ( <div> <h3>Fooer</h3> <p>{count}</p> <button onClick={()=>changeCount(1200)}>修改count</button> </div> ) } }
三、路由跳转
路由跳转我们在函数组件中有两种方案
props.history来进行跳转,默认支持query的传递参数
useHistory来进行路由跳转,默认支持state的方式来进行参数传递
const histroy = useHistory() const gotoPage = ()=>{ // 传递参数不用query histroy.push({ pathname:"/charts", state:{ id:1 } }) // props.history.push({ // pathname:"/charts", // query:{ // id:1 // } // }) }
项目开发
项目开发
技术栈
技术栈没有要求:
React、umijs、Vue2、Vue3开发
由个人独立完成
开发时间
明天开始项目设计(今晚开始搭建项目结构)
项目结构搭建完成后,请把项目上传gitee仓库里面
将我设置为管理员 18623187778@sina.cn
提供一个在线文档,gitee仓库地址共享到文档里面
每天会看 大家提交代码进度。
每天规定完成的任务
业务分析
完成养车平台
介绍背景:
在互联网的加持下,我们形成了很多在线养车平台
天猫养车、途虎养车等等。
汽车保养、汽车配件销售、汽车装饰销售、充电业务、上架入住、RBAC权限系统
一共分为两端:
APP端是客户端,用户登录进去后可以购买汽车周边产品,
美容服务:洗车、抛光、打蜡
维修服务:保养、维修
充电桩:寻找最近充电桩,查看充电桩的情况
PC平台端:系统可以管理所有的商家、管理商品、订单、评价等等
商家端:商家需要入住到平台、商家可以上传自己的店铺商品。可以上架下架等等,还可以添加自己服务
目前只需要完成平台端:
业务1:登录
业务2:商家入驻——-平台审核商家—-商铺审核—充电桩审核—门店管理
业务3:用户管理—-角色管理—菜单管理
业务4:商品管理—商品分类管理(扩展)
业务5:服务类型管理
扩展:
有一个插件querystring,你们可以下载yarn add querystring 。他可以默认蒋一个json转化为字符串; 比如:const user = {id:1,name:"xiaowang"} 通过qs.stringify(user)——> "id=1&name=xiaowang"