目录
react
recat基本概念
react是由facebook开发的
recat是一个专门用于构建用户界面的javascript库
为了解决页面更新dom加载慢的问题 所以推出的react这个js库 来高效的解决大型项目中的dom加载国漫页面渲染过慢的问题
recat特点
声明式设计:我们只需要告诉程序干什么 程序会自己帮我们完成
高效
灵活
组件化
单项数据流
发展史
2013年推出的 在13年9月份 就受到了市场的追捧 15年3月 推出了一个reactNative的技术(使用react写app)
兼容性
ie8以下无效
脚手架创建-cra
cra—create-react-app
1.全局安装cra
npm install -g create-react-app
2.查看版本
create-react-app –version
3.cd到项目下
4.开始创建项目 create-react-app 项目名
5.cd到项目下
6启动 npm start
JSX
我们在react中编写内容使用的是JSX的语法 JSX=javascript and XML
遇见<就会当htm解析 遇到{}当js解析 jsx的外层最好用一对()包裹>
注意:jsx是一个非常严格的语法要求 标签必须遵循w3c规范完成 标签必须关闭
JSX优点:
1.安全可靠 因为在编译的时候就会发现错误
绑定变量
使用{}来进行绑定
<h1>你坏—–{text}</h1>
html的属性插入变量
不要双引号 直接{}
{/* 属性插变量 */}
<a href={ahref}>{btntext}</a>
多行标签
多行标签必须有一个父容器来进行包裹
jsx 注释
{ /*我是jsx的注释*/ }
遍历数据
在react中遍历数据使用map()
{
arr.map((v,i)=>{
return (
<li key={i}>{v}</li>
)
})
}
<table>
{/* 不加会有警告 */}
<tbody>
{
obj.map((v,i)=>{
return (
<tr key={i}>
<td>{v.name}</td>
<td>{v.age}</td>
</tr>
)
})
}
</tbody>
</table>
设置样式
类样式
以为class是es6类的关键字 所以要使用className
引用样式使用import哎进行引用
import React, { Component } from 'react'
export default class homed extends Component {
render() {
return (
<div>
{/* 在react中如果要设置类样式 class必须变成className */}
<h1 className="demoh">类样式</h1>
</div>
)
}
}
引用样式文件
import React, { Component } from 'react'
// 引用css文件
import "./homed.css"
export default class homed extends Component {
render() {
return (
<div>
{/* 在react中如果要设置类样式 class必须变成className */}
<h1 className="demoh">类样式</h1>
</div>
)
}
}
行内样式
行内样式使用对象的方式来进行设置 所以{
{}} 外面的{}是jsx的语法 里面的{}是对象的语法
而且如果多个单词 那么把-去掉 后面的单词首字母大写
import React, { Component } from 'react'
// 引用css文件
import "./homed.css"
export default class homed extends Component {
render() {
return (
<div>
{/* 在react中如果要设置类样式 class必须变成className */}
<h1 className="demoh">类样式</h1>
{/* 行内样式使用对象的方式来进行设置 */}
{/* 而且如果多个单词 那么把-去掉 后面的单词首字母大写 */}
<h1 style={{color:"red",backgroundColor:"pink"}}>行内样式</h1>
</div>
)
}
}
组件
本质 自定义标签
组件的特点优势
强内聚 弱耦合
提高了代码的复用性
减低了测试难度
代码的复杂度也大大降低
组件的分类
在react中他有两种组件的写法
函数组件–无状态组件
语法:
function 函数名(组件名 但是名字首字母必须必须必须必须 必须 大写){
return (
jsx
)
}
类组件–有状态组件
语法:
class 类名(当前组件名首字母大写) extends React.Component{
render(){ render方法也叫做渲染方法 他的作用就是渲染当前的jsx
return(
jsx
)
}
}
props
正向传值
props能修改吗?
props是只读的 不能修改
语法:
props函数组件
语法 :
只需要在函数组件中添加一个怕props的形参 即可使用props
子组件接收
// 子组件需要把props当成形参传入
let Zi=(props)=>{
let {text,num}=props
return(
<div>
zizizziziziziz---{text}--{num}
</div>
)
}
export default Zi
父组件传递
import Fz from "./funpropszi.jsx"
let Fu=()=>{
let obj={text:"text数据",num:"num数据"}
return(
<div>
fufufufuffufufu
{/* 传递参数可是用扩展运算符 */}
<Fz {...obj}/>
</div>
)
}
export default Fu
props类组件
this.props.xxx
1.子组件设置props
import React, { Component } from 'react'
export default class propszi extends Component {
render() {
return (
<div>
zizizzizizizizzz---{this.props.text}
<br />
{this.props.num}
</div>
)
}
}
2.父组件传递
import React, { Component } from 'react'
import Pz from "./propszi.jsx"
export default class propsfu extends Component {
render() {
return (
<div>
fuffufufufufuffu
{/* 父组件传值 */}
<Pz text="我是父组件的text" num="我是父组件的num"/>
</div>
)
}
}
上面的写法太low了 改变一下
子组件接收数据的时候 使用解构赋值的方式取出this。props的数据
import React, { Component } from 'react'
export default class propszi extends Component {
render() {
// 由于this.props是一个对象 为了让代码看起来更加简洁 所以我们
// 使用解构赋值的方式 把props中的数据快速取出来
let {text,num}=this.props
return (
<div>
zizizizizizizziiz---{text}---{num}
</div>
)
}
}
父组件传递
import React, { Component } from 'react'
import Pz from "./propszi.jsx"
export default class propsfu extends Component {
render() {
let obj={text:"我是父组件的text",num:"我是父组件的num"}
return (
<div>
fuffufufufufuffu
{/* 父组件传值 使用扩展运算符可以简化我们在父组件给
子组件传递数据时候的复杂度
*/}
<Pz {...obj} />
</div>
)
}
}
props 验证
rccp
注意:
自 React v15.5 起,
React.PropTypes
已移入另一个包中。请使用 [
prop-types
库](
prop-types – npm
) 代替。
// import React, { Component } from 'react'
// export default class propszi extends Component {
// render() {
// // 由于this.props是一个对象 为了让代码看起来更加简洁 所以我们
// // 使用解构赋值的方式 把props中的数据快速取出来
// let {text,num}=this.props
// return (
// <div>
// zizizizizizizziiz---{text}---{num}
// </div>
// )
// }
// }
import React, { Component } from 'react'
// 包要引
import PropTypes from 'prop-types'
export default class propszi extends Component {
// 使用PropTypes来进行props的验证
static propTypes = {
text: PropTypes.string,
num:PropTypes.number
}
render() {
let {text,num}=this.props
return (
<div>
zizizizizizizziiz---{text}---{num}
</div>
)
}
}
状态机state
状态==数据
状态机===数据机制
数据等同于状态 状态改变页面也会改变
函数组件可以使用状态码?
函数组件默认情况下不能使用状态 后面会学到高阶语法 HOOK才能使用
语法
状态创建
import React, { Component } from 'react'
export default class statedemo extends Component {
// 创建状态 需要写在constructor中
// 在es6中 子类写不写constructor 在实例的过程中 都会给补上他
// 但是如果我们写了 那么必须写super()
// super就是调用父类构造方法 只有写了子类才有自己的this
// 如果只写了constructor不写super()那么以后使用this的时候指向都是错误的
constructor(){
super()
// 创建状态
this.state={
text:"字符串",
num:18,
arr:[111,2222,33333],
obj:{name:"xxixi"}
}
}
render() {
return (
<div>
<h1>状态的使用</h1>
</div>
)
}
}
读取状态
在想使用的地方使用 this.state.xxx
import React, { Component } from 'react'
export default class statedemo extends Component {
// 创建状态 需要写在constructor中
// 在es6中 子类写不写constructor 在实例的过程中 都会给补上他
// 但是如果我们写了 那么必须写super()
// super就是调用父类构造方法 只有写了子类才有自己的this
// 如果只写了constructor不写super()那么以后使用this的时候指向都是错误的
constructor(){
super()
// 创建状态
this.state={
text:"字符串",
num:18,
arr:[111,2222,33333],
obj:{name:"xxixi"}
}
}
render() {
return (
<div>
<h1>状态的使用</h1>
{/* 使用状态 */}
<em>{this.state.num}-----{this.state.text}---{this.state.arr[1]}</em>
</div>
)
}
}
修改state
修改state不能直接使用=修改 而是要调用一个叫 setState()来进行修改
import React, { Component } from 'react'
export default class statedemo extends Component {
// 创建状态 需要写在constructor中
// 在es6中 子类写不写constructor 在实例的过程中 都会给补上他
// 但是如果我们写了 那么必须写super()
// super就是调用父类构造方法 只有写了子类才有自己的this
// 如果只写了constructor不写super()那么以后使用this的时候指向都是错误的
constructor(){
super()
// 创建状态
this.state={
text:"字符串",
num:18,
arr:[111,2222,33333],
obj:{name:"xxixi"}
}
}
// 创建函数
fun=()=>{
// 修改state数据必须使用setState()
this.setState({
num:888,
text:"我变了"
})
}
render() {
return (
<div>
<h1>状态的使用</h1>
{/* 使用状态 */}
<em>{this.state.num}-----{this.state.text}---{this.state.arr[1]}</em>
<button onClick={this.fun}>点我修改</button>
</div>
)
}
}
为什么要使用setState()来修改state
因为调用了setState之后 他会自动触发render方法重新渲染页面 从而让数据改变之后 页面页也会发生改变
setState 在修改数据的时候是异步的
由于setState是一个异步的 所以他第一个参数是一个对象 用来修改数据 第二个参数是一个回调函数 是当修改完数据之后自动触发回调函数
this.setState({
num:888,
text:"我变了"
},()=>{
// 我就是想等数据修改之后在console中打印出来
console.log(this.state.num);
})
ref
用来标识组件内部的元素 但是注意 函数组件由于没有实例所以不能使用ref
使用
1.字符串方式(已经淘汰了)
2.回调函数方式
import React, { Component } from 'react'
export default class refdemo extends Component {
fun=()=>{
console.log(this.refinput.value);
}
render() {
return (
<div>
<h1>ref回调函数的方式</h1>
{/* 回调函数方式绑定ref 需要在dom节点上挂载一个函数
函数的形参是当前dom节点 (形参)=>{随便创建一个变量=形参}*/}
<input type="text" ref={(demoinput)=>{this.refinput=demoinput}}/>
<button onClick={this.fun}>点我得到值</button>
</div>
)
}
}
3.React.createRef() react16.3新增
import React, { Component } from 'react'
export default class refdemob extends Component {
constructor(){
super()
// 1.创建createRef
this.refdemo=React.createRef()
}
fun=()=>{
// 3.使用
console.log(this.refdemo.current.value);
}
render() {
return (
<div>
<h1>createRef()</h1>
{/* 2.绑定 */}
<input type="text" ref={this.refdemo}/>
<button onClick={this.fun}>点我得到值</button>
</div>
)
}
}
事件处理机制
事件的绑定
使用小驼峰命名法来进行事件的绑定 onclick—->onClick
如果事件要调用函数 那么函数是不加()
函数实参传递
由于react中的函数是没有()的 所以传统的实参传参方式是没有办法进行的
.bind方式进行实参的传递
onClick={this.fun.bind(this,你要传递的数据)}
事件箭头函数调用函数传参
{/* 通过箭头函数调用函数传参 */} <button onClick={()=>{this.funb("参数")}}>点我传参2</button>
事件的修饰
react中如果想阻止事件传播或者默认行为的话 使用同原生一样的方式
this指向修改
1.就是创建函数的时候使用箭头函数创建
2.通过.bind方式解决this
<button onClick={this.funb.bind(this)}>bind修改this</button>
3.把函数的调用变成使用箭头函数调用
<button onClick={()=>{this.funb()}}>箭头函数调用函数</button>
4.在constructor中提前绑定
constructor(){
super()
this.state={
text:"你好"
}
// 提前给函数绑定this
this.funb=this.funb.bind(this)
}
条件渲染
根据我们的需要 显示或者隐藏某写内容
1.三元运算符
import React, { Component } from 'react'
export default class democ extends Component {
constructor(){
super()
this.state={
bool:true
}
}
render() {
return (
<div>
<h1>条件渲染</h1>
{this.state.bool?<h1>吃了</h1>:<h1>没有吃</h1>}
</div>
)
}
}
2.if全家桶
注意注意注意
:在jsx不能写if
import React, { Component } from 'react'
export default class demod extends Component {
constructor(){
super()
this.state={
num:5
}
}
render() {
let com=""
if(this.state.num==1){
com= <h1>第1</h1>
}else if(this.state.num==2){
com= <h1>第2</h1>
}else if(this.state.num==3){
com= <h1>第3</h1>
}else if(this.state.num==4){
com= <h1>第4</h1>
}else if(this.state.num==5){
com= <h1>第5</h1>
}
return (
<div>
<h1>条件渲染</h1>
{com}
</div>
)
}
}
状态提升
react中转台提升就是
多个组件需要反映相同的数据变化
我们可以把这个数据提升到这几个组件的父组件之上 然后在父组件中改变数据 那么通过props分发给这几个子组件即可反映相同的数据变化
扩展—-多行标签-空标签
在react中 多行标签必须有一个父容器包裹 但是往往这些包裹的容器标签是没有用 在页面渲染的时候 会造成冗余代码
1.真正的空标签
import React, { Component } from 'react'
export default class ezia extends Component {
render() {
return (
// 空标签
<>
<h1>我是h1</h1>
<h1>我是h11</h1>
</>
)
}
}
2.Fragment 空标签 今后会使用的比较多
import React, { Component,Fragment } from 'react'
export default class ezi extends Component {
render() {
return (
// Fragment空标签 别忘了引用
<Fragment>
<h2>h222222</h2>
<h2>h222222222222222</h2>
</Fragment>
)
}
}
扩展—-强制刷新
react 只有使用state创建的变量 在使用setState修改之后页面才会更新。
如果我就是不想在state里面写状态 也不想使用setState修改 我怎么样创建变量修改更新呢?
import React, { Component } from 'react'
export default class demof extends Component {
constructor(){
super()
this.name="xixi"
}
fun=()=>{
this.name="haha"
console.log(this.name);
// 强制触发render()渲染
// 只要调用就会重新渲染
this.forceUpdate()
}
render() {
return (
<div>
<h1>页面强制刷新---{this.name}</h1>
<button onClick={()=>{this.fun()}}>点我修改</button>
</div>
)
}
}
组件传值
正向传值–props
详见上面内容
逆向传值
react的逆向传值 使用的也是props 只是和正向传值不同 因为正向传值传递的是数据 但是逆向传值传递的是方法
子组件
import React, { Component } from 'react'
export default class nizi extends Component {
render() {
return (
<div>
<h1>子组件</h1>
{/* 1.子组件必须通过事件来触发逆向传值 */}
{/* 2.子组件需要接收父组件传递过来的函数 并且使用bind进行函数实参传递 */}
<button onClick={this.props.fun.bind(this,"子组件的数据")}>点我逆向传值</button>
</div>
)
}
}
父组件
import React, { Component } from 'react'
import Nz from "./nizi.jsx"
export default class nifu extends Component {
// 4.创建父组件的函数
demo=(text)=>{
console.log(text);
}
render() {
return (
<div>
<h1>逆向传值</h1>
{/* 3.子组件需要一个函数 那么父组件给他传递一个函数 */}
<Nz fun={this.demo}/>
</div>
)
}
}
同胞传值—pubsub-js
1.下载pubsub-js npm install –save pubsub-js
2.在需要传递的组件中进行数据的抛出(
使用自定义事件)publish(“自定义事件名”,”传递的参数”)
import React, { Component,Fragment } from 'react'
// 1.引用pubsub-js
import PubSub from "pubsub-js"
export default class pubsubzia extends Component {
fun=()=>{
// 2.抛出自定事件
PubSub.publish("pao","我是zia的数据么么哒!!!")
}
render() {
return (
<Fragment>
<h1>zia</h1>
<button onClick={this.fun}>点我同胞传值</button>
</Fragment>
)
}
}
3.在需要数据的组件内
监听自定义事件 subscribe(“你要监听的事件名”,(事件,数据)=>{})
import React, { Component } from 'react'
import PubSub from "pubsub-js"
export default class pubsubzib extends Component {
// 3使用生命周期自动调用subscribe()来监听自定事件
// 组件渲染之前
componentWillMount() {
PubSub.subscribe("pao",(a,b)=>{
console.log(a);//监听的事件名
console.log(b);//监听的数据
})
}
// 组件渲染之后
componentDidMount() {
}
render() {
return (
<>
<h1>zib</h1>
</>
)
}
}
跨组件传值
react中组件传递参数是通过props一层一层的进行传递的 数据也是单向传递的 如果出现了跨层级的关系 要是按照传统的方式传参 太麻烦了
方式1 context上下文对象
就是react中为了解决跨层级传值出现的一个技术 降低了数据传递的复杂度 传递起来更加的高效
上下文对象 有两个内容
Provider 生产者 —– 》 创建要传递的数据
Consumer 消费者 —— 》 使用传递的数据
使用:
1.使用上下文对象 我们可以创建一个文件夹用来容纳他 并且创建一个文件写一个组件
import React, { Component } from 'react'
// 建议组件名大写
export default class Index extends Component {
render() {
return (
<div>
</div>
)
}
}
2.创建出上下文对象以及他的生产者和消费者
// 1.引用进创建上下文对象的createContext
import React, { Component,createContext } from 'react'
// 2.创建上下文对象
let context=createContext()
// 3.创建生产者和消费者
let {Provider,Consumer}=context;
// 建议组件名大写
class Index extends Component {
render() {
return (
<div>
{/* 4.this.props.children 表示所有的子元素 */}
{this.props.children}
</div>
)
}
}
// 5.修改暴漏 需要暴漏当前组件与 消费着
export {Index,Consumer}
3.我们需要传递数据 那么就把当前这个上下文对象的组件变成所有组件的老大
在index.js中设置
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 6.引用并且解构出上下文对象的组件
import {Index} from "./context/index.js"
import App from './components/ye.jsx';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
// 7.让上下文对象变成所有组件的老大
<Index>
<App />
</Index>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
4.生产数据
// 1.引用进创建上下文对象的createContext
import React, { Component,createContext } from 'react'
// 2.创建上下文对象
let context=createContext()
// 3.创建生产者和消费者
let {Provider,Consumer}=context;
// 建议组件名大写
class Index extends Component {
render() {
return (
<div>
{/* 8上产数据 */}
<Provider value={{text:"数据1",num:666}}>
{/* 4.this.props.children 表示所有的子元素 */}
{this.props.children}
</Provider>
</div>
)
}
}
// 5.修改暴漏 需要暴漏当前组件与 消费着
export {Index,Consumer}
5.消费数据
import React, { Component } from 'react'
import Sun from "./sun.jsx"
// 9引用消费者
import {Consumer} from "../context/index.js"
export default class fu extends Component {
render() {
return (
<div>
{/* 10使用消费者 */}
<Consumer>
{
// (就是生产者的数据)=>{
// 想怎么用就怎么用
// }
(data)=>{
return (
<h1>{data.num}</h1>
)
}
}
</Consumer>
爸爸组件
<Sun/>
</div>
)
}
}
方式2 redux
redux是js的一个状态管理工具
他是第三方的 可以在react中用 也可以在vue中使用
redux的三大原则
1.单一数据源 整个项目只有一个store对象
2.state是只读的 在redux想改变state那么必须使用action来进行修改 不能直接修改
3.使用纯函数来执行修改 在redux中为了要修改数据 那么我们需要编写一个reducer的纯函数
redux的执行流程
详见1203课件图片
redux中常见的属性
store : redux对象管理者整个项目的状态
reducer: 是一个函数 保存着就是数据与数据修改的方法
action: 是redux唯一可以修改数据
createStore() 创建redux对象
getState() 获取数据
dispatch() 触发action
基本使用
1.下载 npm install –save redux
2.创建store文件夹 用来容纳redux对象
// 1.引入createStore
import {createStore} from "redux"
// 6.创建数据
let data={
name:"xixi",
age:18
}
// 4.创建reducer 存储的是数据与修改数据的动作
// state 数据
// action修改动作
// 7。把数据赋值给state
let reducer=(state=data,action)=>{
return state
}
// 2.创建出store对象
// 5.把数据与数据修改 注入到redux对象中
let store=createStore(reducer)
// 3.暴漏
export default store
读取数据
import React, { Component } from 'react'
// 1.先引用
import store from "../store/index.js"
export default class reduxdemo extends Component {
constructor(){
super()
this.state={
// 2.store.getState.xxx来读取数据
text:store.getState().name
}
}
render() {
return (
<div>
<h1>redux使用---{this.state.text}</h1>
</div>
)
}
}
修改数据
1.页面通过dispatch触发修改的动作
import React, { Component } from 'react'
// 1.先引用
import store from "../store/index.js"
export default class reduxdemo extends Component {
constructor(){
super()
this.state={
// 2.store.getState.xxx来读取数据
text:store.getState().name
}
}
fun=()=>{
// 数据的修改1 通过dispatch触发修改
// store.dispatch({type:"你要触发的修改动作名"})
store.dispatch({type:"REDUX_USER_LIST_UPDATE"})
}
render() {
return (
<div>
<h1>redux使用---{this.state.text}</h1>
<button onClick={()=>{this.fun()}}>点我修改数据</button>
</div>
)
}
}
2.来到reducer中创建数据的修改动作
// 1.先引用 创建store的方法 createStore
import {createStore} from "redux"
// 6.创建数据
let xiaoming={
text:"text数据",
num:666
}
// 5创建一个函数 包含了数据与数据的修改状态 7传入数据
let reducer=(state=xiaoming,action)=>{
// 数据修改2 在action中设置多个修改动作
switch (action.type) {
case "REDUX_USER_LIST_UPDATE":
// 修改动作
return {...state,num:"我变了"}
break;
default:
return state
break;
}
}
// 2.创建store对象 4.使用数据与修改数据的reducer
let store=createStore(reducer)
// 3.暴漏
export default store
当时发现页面并没有变化 是因为数据变了 但是页面并没有执行render渲染所以页面没有变
3 subscribe() redux中用来监听state数据变化的 当state的数据变化了 那么subscricbe就会触发执行
import React, { Component } from 'react'
// 1.先引用
import store from "../store/index.js"
export default class reduxdemo extends Component {
constructor(){
super()
this.state={
// 2.store.getState.xxx来读取数据
text:store.getState().num
}
}
componentDidMount() {
// 监听store的state是否变了
store.subscribe(()=>{
// 如果redux的数据变了 那么这个函数就会执行
// 我们就触发render渲染
this.setState({
text:store.getState().num
})
})
}
fun=()=>{
// 数据的修改1 通过dispatch触发修改
// store.dispatch({type:"你要触发的修改动作名"})
store.dispatch({type:"REDUX_USER_LIST_UPDATE"})
}
render() {
return (
<div>
<h1>redux使用---{this.state.text}</h1>
<button onClick={()=>{this.fun()}}>点我修改数据</button>
</div>
)
}
}
redux拆分写法
使用actionCreator来管理修改任务动作
我们会发现组件中有很多的调用修改的dispatch如下
fun=()=>{
// 数据的修改1 通过dispatch触发修改
// store.dispatch({type:"你要触发的修改动作名"})
store.dispatch({type:"REDUX_USER_LIST_UPDATE"})
}
由于组件增多 可能回后期维护比较麻烦 所以我们可以把dispach的任务动作名 提取出来创建一个文件同一管理项目的人物动作名
1.在store下创建一个文件叫actioncreator.js来保存动作名
export let REDUX_USER_LIST_UPDATE=()=>{
return {type:"REDUX_USER_LIST_UPDATE"}
}
2.组件的dispatch使用
在组件中先引用
// actioncreator引用
import {REDUX_USER_LIST_UPDATE} from "../store/actionCreator.js"
在dispatch中使用
fun=()=>{
// 数据的修改1 通过dispatch触发修改
// store.dispatch({type:"你要触发的修改动作名"})
store.dispatch(REDUX_USER_LIST_UPDATE())
}
完整
import React, { Component } from 'react'
// 1.先引用
import store from "../store/index.js"
// actioncreator引用
import {REDUX_USER_LIST_UPDATE} from "../store/actionCreator.js"
export default class reduxdemo extends Component {
constructor(){
super()
this.state={
// 2.store.getState.xxx来读取数据
text:store.getState().num
}
}
componentDidMount() {
// 监听store的state是否变了
store.subscribe(()=>{
// 如果redux的数据变了 那么这个函数就会执行
// 我们就触发render渲染
this.setState({
text:store.getState().num
})
})
}
fun=()=>{
// 数据的修改1 通过dispatch触发修改
// store.dispatch({type:"你要触发的修改动作名"})
store.dispatch(REDUX_USER_LIST_UPDATE())
}
render() {
return (
<div>
<h1>redux使用---{this.state.text}</h1>
<button onClick={()=>{this.fun()}}>点我修改数据</button>
</div>
)
}
}
使用actionType来同一管理任务动作的名字
由于这个动作的名字出现了很多次 所以我们需要把任务动作名进行封装
1.新建actionTypes.js文件
export const TYPE_REDUX_USER_LIST_UPDATE="REDUX_USER_LIST_UPDATE"
2.在action的switch中和 actionCreatr.js进行引用使用
封装reducer
今后项目的体积会逐渐变大 那么我们把所有的数据与修改写在一个文件中后期不好管理 所以我们可以把每个组件的数据与修改动作剥离出来 单独进行管理
1.创建一个文件夹用来容纳模块
2.把原来写在store文件夹下的render提取到模块文件中
import {TYPE_REDUX_USER_LIST_UPDATE} from "./actionType.js"
let xiaoming={
text:"text数据",
num:666
}
let reduxdemom=(state=xiaoming,action)=>{
switch (action.type) {
case TYPE_REDUX_USER_LIST_UPDATE:
console.log({...state,num:"我变了"});
return {...state,num:"我变了"}
break;
default:
return state
break;
}
}
export default reduxdemom
合并reducer
因为我们拆分成模块之后 就会出现多个reducer 那么我们需要把多个合并成一个
1.新建一个reducer.js
2.在其中开始合并
// 1.把你要合并的所有模块引进来
import reduxdemom from "./modules/reduxdemom.js"
// 2.引用合并功能 combineReducers把多个reducer合并成一个
import {combineReducers} from "redux"
// 3.开始合并
let reducer=combineReducers({
reduxdemom
})
// 4.暴漏
export default reducer
3.在store文件中引用使用
发现数据不展示了 原因是因为我们现在使用了模块 所以在读数据的时候 就需要使用
store.getState.模块名.xxxxx
react-redux
在写的时候原有的store文件夹下的所有内容同redux
因为redux是第三方的 所以我们在使用的时候会发现组件中有大量的store对象
react-redux 就是一个专门为react开发的redux状态管理工具
下载 : npm install –save react-redux
使用
1.我们可以使用react-redux中的Provider 用来把store对象分发给所有的组件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/demo.jsx';
import reportWebVitals from './reportWebVitals';
// 1.引用react-redux拿出Provider
import {Provider} from "react-redux"
// 2.引用store对象
import store from "./store/index.js"
ReactDOM.render(
// 3.使用Provider中的store属性进行传递
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
2.使用connect()把当前的组件与react-redux连接起来
import React, { Component } from 'react'
// 1.引用一下连接
// connect 是一个react中的高阶组件 高阶组件就是需要接收一个组件为参数才可以使用
import {connect} from "react-redux"
class reactredux extends Component {
render() {
return (
<div>
<h1>react-redux的使用</h1>
</div>
)
}
}
// connect是一个方法 只有当这个方法被调用之后才是一个高阶组件
export default connect(state=>({state}))(reactredux)
3.读取数据
<h1>react-redux的使用--{this.props.state.模块名.你的数据名}</h1>
修改
this.props.dispatch() 直接调用修改不需要在向redux一样去监听了
fun=()=>{
// 修改
this.props.dispatch(REDUX_UPDATE())
}
路由
根据url的不同来切换不同的组件页面
从而完成SPA单页面应用的一个技术 整个项目只有一个完整的html页面 那么基于这个页面 组件在其上面进行根据url来渲染不同的组件页面 页面切换的时候不会刷新 用户就不会感知到页面的切换 从而更加让我们的项目贴近原生的体验
路由分类
react-router 仅仅只有基本的路由功能api
react-router-dom 除了基本路由的api之外还有更多便捷性的api
下载 cnpm install –save react-router-dom@5.2.0
使用
1.新建对应的路由文件夹 router 与views文件夹
2.新建路由页面组件
3.设置路由模式 在index.js中进行设置
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 1.引用路由模式
import {BrowserRouter} from "react-router-dom"
ReactDOM.render(
// 2.设置路由模式
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
4.配置规则与出口
<Route path=”/路径” component={匹配的组件页面}/>
import React, { Component } from 'react'
// 1.引用你要配置的路由
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import Shop from "../views/shop.jsx"
// 2.引用Route来进行出口与规则的配置
import {Route} from "react-router-dom"
export default class index extends Component {
render() {
return (
<div>
{/* 3.配置 */}
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
</div>
)
}
}
5.把路由规则的组件变成跟组件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 最后一部
import Router from './router/index.js';
import reportWebVitals from './reportWebVitals';
// 1.引用路由模式
import {BrowserRouter} from "react-router-dom"
ReactDOM.render(
// 2.设置路由模式
<BrowserRouter>
<Router />
</BrowserRouter>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
路由模式
HashRouter 带#号 刷新不丢失 兼容性好
BrowserRouter 不带# 刷新丢失 兼容性低
路由导航
编程式
声明式
Link to属性来设置你要跳转的具体路径
import React, { Component } from 'react'
// 1.引用你要配置的路由
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import Shop from "../views/shop.jsx"
// 2.引用Route来进行出口与规则的配置
import {Route,Link} from "react-router-dom"
export default class index extends Component {
render() {
return (
<div>
{/* 不要忘了上面要引用Link才能使用 */}
<Link to="/home">点我去home</Link>
<Link to="/phone">点我去phone</Link>
<Link to="/user">点我去user</Link>
<Link to="/shop">点我去shop</Link>
{/* 3.配置 */}
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
</div>
)
}
}
NavLink to属性来设置你要跳转的具体路径 NavLink会在选中之后添加一个active的类名
import React, { Component } from 'react'
// 1.引用你要配置的路由
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import Shop from "../views/shop.jsx"
// 2.引用Route来进行出口与规则的配置
import {Route,NavLink} from "react-router-dom"
export default class index extends Component {
render() {
return (
<div>
{/* 不要忘了上面要引用NavLink才能使用 */}
<NavLink to="/home">点我去home</NavLink>
<NavLink to="/phone">点我去phone</NavLink>
<NavLink to="/user">点我去user</NavLink>
<NavLink to="/shop">点我去shop</NavLink>
{/* 3.配置 */}
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
</div>
)
}
}
注意 如果有的同学navlink的动态类名出不来 那么就不要用vscode的内置终端启动项目 在外部的cmd启动
修改NavLink动态添加的类名
使用activeClassName属性来进行修改
<NavLink to="/home" activeClassName="xiaoming">点我去home</NavLink>
404
1.新建404页面
2.配置 404页面需要放到最下面进行配置
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
{/* 404页面需要放到最下面进行配置 */}
<Route component={No}/>
注意: 当我们写上404页面之后 会发现 无论那个页面显示都会出现404 原因是因为 在react中 匹配上页面之后 他不会停止 会一直向下执行 404页面就会被渲染上
Switch 唯一渲染
路由渲染一个之后就不渲染后续的内容了
{/* 唯一渲染包裹所有的路由出口与规则 */}
<Switch>
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
{/* 404页面需要放到最下面进行配置 */}
<Route component={No}/>
</Switch>
重定向 redirect
exact精准匹配 只有路径为/的时候才能匹配到
<Redirect from=”/” to=”去哪里” exact/>
<Switch>
<Route path="/home" component={Home}/>
<Route path="/phone" component={Phone}/>
<Route path="/user" component={User}/>
<Route path="/shop" component={Shop}/>
{/* 重定向 */}
<Redirect from="/" to="/home" exact/>
{/* 404页面需要放到最下面进行配置 */}
<Route component={No}/>
</Switch>
二级或者多级路由
1.新建页面
2.确定是当前二级路由的父级是那个组件 然后在其当中 直接来使用Route配置规则与出口
import React, { Component } from 'react'
// 1.需要把二级路由引进来
import Era from "./er/era.jsx"
import Erc from "./er/erc.jsx"
import Erd from "./er/erd.jsx"
// 2.需要引用Route
import {Route,NavLink} from "react-router-dom"
export default class shop extends Component {
render() {
return (
<div>
{/* 二级路由的导航 */}
<NavLink to="/shop/era">点我去a</NavLink>
<NavLink to="/shop/erc">点我去c</NavLink>
<NavLink to="/shop/erd">点我去d</NavLink>
shop
{/* 二级路由的规则 */}
<Route path="/shop/era" component={Era}/>
<Route path="/shop/erc" component={Erc}/>
<Route path="/shop/erd" component={Erd}/>
</div>
)
}
}
路由传参/动态路由匹配
params
-
在规则中配置接收参数
{/* 在需要接收参数的路由规则中设置接收参数 */} <Route path="/phone/:xiaoming" component={Phone}/>
-
发送`
import React, { Component } from 'react'
import Tb from "../components/topbar.jsx"
import {Link} from "react-router-dom"
export default class home extends Component {
fun=()=>{
// 编程式方式
this.props.history.push("/phone/我是参数")
}
render() {
return (
<div>
<Tb/>
home
{/* params传参 */}
{/* 声明式 */}
<Link to="/phone/我是参数">使用parms方式进行路由的传参</Link>
{/* 编程式 */}
<button onClick={()=>{this.fun()}}>点我编程式方式params传参</button>
</div>
)
}
}
3.接收
this.props.match.params.xxx
import React, { Component } from 'react'
export default class phone extends Component {
render() {
return (
<div>
{/* 接收params传参 */}
phone---{this.props.match.params.xiaoming}
</div>
)
}
}
params方式发送数据 只能传递字符串 并且传递数据的时候url会显示参数 不好看 刷新页面数据不丢失
state
1.发送
import React, { Component } from 'react'
import {Link} from "react-router-dom"
export default class era extends Component {
render() {
return (
<div>
era
{/* state方式路由传参 */}
{/* 声明式 */}
{/* <Link to={{pathname:"你去的地址",state:{key:val}}}>点我state传递参数</Link> */}
<Link to={{pathname:"/shop/erc",state:{xiaohong:"我是state发送的数据"}}}>点我state传递参数</Link>
{/* 编程式 */}
</div>
)
}
}
2.接收
this.props.location.state.xxx
import React, { Component } from 'react'
export default class erc extends Component {
render() {
return (
<div>
{/* state接收参数 */}
erc--{this.props.location.state.xiaohong}
</div>
)
}
}
state 在发送参数的时候url是不显示传递的数据的 可以发送对象等多个数据 刷新页面数据会丢失
路由的render渲染
就是在路由跳转渲染之前 我们提供了一个写逻辑代码的场地
` `{/* render渲染写法 */}
<Route path="/rd" render={()=>{
return false?<Rd/>:<Redirect to="/home"/>
}}/>
HOC 高阶组件
在react组件的创建过程中经常需要我们把多个组件中的一类功能进行重复的使用 我们其实就可以把这一类相同的功能进行提取公用 这样一来 我们在编写组件的时候就可以重复使用这一类提取的公共内容 减少了代码量与后期代码的维护难度。
HOC高阶组件——》参数是一个组件 并且返回值还是一个组件
使用:
1.创建文件夹用来容纳HOC的代码
// hoc 参数是一个组件 返回值还是一个组件
import React, { Component } from 'react'
let Hocdemo=(El)=>{ //因为参数今后传递过来的是一个组件所以首字母大写
return class element extends Component {
render() {
return (
<div>
<El/>--来自于2111班
</div>
)
}
}
}
export default Hocdemo
2.使用 在你想使用高阶组件复用的组件之内
import React, { Component } from 'react'
// 1.先引用HOC
import Hocdemo from "../hoc/element.js"
// 2.修改暴露到最下面
class hoca extends Component {
render() {
return (
<div>
aaaaaaaaaaaaaaaaaaa
</div>
)
}
}
// 3.暴露高阶组件 并且把当前组件当成高阶组件的参数传入
export default Hocdemo(hoca)
高阶组件—反向继承
HOC的反向继承 其中最核心的就是渲染劫持(拦截了渲染 可以在高阶组件中进行条件判断)
// hoc 参数是一个组件 返回值还是一个组件
import React, { Component } from 'react'
// 高阶组件里面需要条件渲染 那么其中需要条件
let Hocdemo=(El,num)=>{
// 如果要反向继承 那么首先 我们在返回的组件中就不能使用如下写法了
// return class element extends Component {
// 而是要使用
return class element extends El {
render() {
return (
<div>
<El/>
{num>=18? <h1>欢迎您尊敬的vip!!!</h1>:<h1>您不满18禁止浏览!!!</h1>}
</div>
)
}
}
}
export default Hocdemo
使用 传递条件为参数
import React, { Component } from 'react'
// 1.先引用HOC
import Hocdemo from "../hoc/element.js"
// 2.修改暴露到最下面
class hoca extends Component {
render() {
return (
<div>
aaaaaaaaaaaaaaaaaaa
</div>
)
}
}
// 3.暴露高阶组件 并且把当前组件当成高阶组件的参数传入
export default Hocdemo(hoca,19)
扩展–插入字符串 标签
import React, { Component } from 'react'
export default class erd extends Component {
constructor(){
super()
this.state={
newhtml:"<em>你好么么哒</em>"
}
}
render() {
return (
<div>
erd
<div>
{/* 直接 渲染这个html字符串 那么不会去解析这个标签 */}
{this.state.newhtml}
</div>
{/* 我们如果想插入的话 需要使用如下语法
但是注意 html前面是 两个底杠 不要写错了
*/}
<div dangerouslySetInnerHTML={{__html:this.state.newhtml}}>
</div>
</div>
)
}
}
样式组件化–styled-components
styled-components 样式化组件,主要作用是它可以编写实际的CSS代码来设计组件样式,也不需要组件和样式之间的映射,即创建后就是一个正常的React 组件,并且可以附加样式给当前组件。 优化react组件
通过 js 赋能解决了原生 css 所不具备的能力,比如变量、循环、函数等
CSS代码时都有哪些痛点
全局污染 – CSS的选择器是全局生效的,所以在class名称比较简单时,容易引起全局选择器冲突,导致样式互相影响。
命名混乱 – 因为怕全局污染,所以日常起class名称时会尽量加长,这样不容易重复,但当项目由多人维护时,很容易导致命名风格不统一。
样式重用困难 – 有时虽然知道项目上已有一些相似的样式,但因为怕互相影响,不敢重用。
代码冗余 – 由于样式重用的困难性等问题,导致代码冗余。
潮流
现在随着组件化概念的流行,对从组件层面维护CSS样式的需求日益增大,CSS-in-JS就是在组件内部使用JavaScript对CSS进行了抽象,可以对其声明和加以维护。这样不仅降低了编写CSS样式带来的风险,也让开发变得更加轻松。它和CSS Modules的区别是不再需要CSS样式文件。
开发
1.下载:npm install –save styled-components
2.创建文件夹荣来容纳样式组件代码
import styled from "styled-components"
//首字母大写
export let Stylehome=styled.div`
h1{
color:red;
}
`
3.在想使用样式的组件中引用 并且进行使用
import React, { Component } from 'react'
// 引用样式组件
import {Stylehome} from "../styled/index.js"
export default class demo extends Component {
render() {
return (
<React.Fragment>
{/* 当成标签的方式进行使用 */}
<Stylehome>
<h1>我是一个内容</h1>
</Stylehome>
<h1>我是2个内容</h1>
</React.Fragment>
)
}
}
typescript
TypeScript简介
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上
TypeScript扩展了JavaScript的语法
解决JavaScript的“痛点”:弱类型和没有命名空间,导致很难模块化。
-
TypeScript是JavaScript的超集。
-
它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
-
TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
-
TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
-
相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。
为什么要用TypeScript
开源
简单 TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。
兼容性好 TScript 是 JS的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JS,所以 TScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。任何现有的JS程序可以不加改变的在TScript下工作。
TypeScript 开发环境搭建
-
下载Node.js
-
安装Node.js
-
使用npm全局安装typescript
-
进入命令行
-
输入:npm i -g typescript
-
tsc空格-v命令用来测试是否安装成功
-
-
创建一个ts文件
-
使用tsc对ts文件进行编译
-
进入命令行
-
进入ts文件所在目录
-
执行命令:tsc xxx.ts
-
通过配置编译
每次写完ts文件都要输入一次命令是不是很麻烦呢,能不能保存文件时就自动编译运行ts文件呢
-
cd到项目下
-
使用
tsc -init
会生成tsconfig.json 文件 (
tsconfig.json文件与TypeScript编译器(tsc)的配置相对应
是 T
ypeScript 使用 tsconfig.json 文件作为其配置文件
用来 指定待编译文件和定义编译选项
。 ) -
直接使用tsc命令即可编译
为什么要使用这个文件
通常我们可以使用
tsc
命令来编译少量 TypeScript 文件
但如果实际开发的项目,很少是只有单个文件,当我们需要编译整个项目时,就可以使用 tsconfig.json 文件,将需要使用到的配置都写进 tsconfig.json 文件,
这样就不用每次编译都手动输入配置,另外也方便团队协作开发
。
常见配置
{
"compilerOptions": {
"target": "ES5", // 目标语言的版本
"module": "commonjs", // 指定生成代码的模板标准
"noImplicitAny": true, // 不允许隐式的 any 类型
"removeComments": true, // 删除注释
"preserveConstEnums": true, // 保留 const 和 enum 声明
"sourceMap": true, // 生成目标文件的sourceMap文件(简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便)
"outDir":"./out/" //编译输出的文件夹
},
"files": [ // 指定待编译文件(files 配置项值是一个数组,用来指定了待编译文件,即入口文件。入口文件依赖其他文件时,不需要将被依赖文件也指定到 files 中,因为编译器会自动将所有的依赖文件归纳为编译对象,即 index.ts 依赖 user.ts 时,不需要在 files 中指定 user.ts , user.ts 会自动纳入待编译文件。)
"./src/index.ts"
]
}
常见配置
compilerOptions
-
编译选项是配置文件中非常重要也比较复杂的配置选项
-
在compilerOptions中包含多个子选项,用来完成对编译的配置
-
项目选项
-
target
-
设置ts代码编译的目标版本
-
可选值:
-
ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
-
-
示例:
-
"compilerOptions": { "target": "ES6" }
-
如上设置,我们所编写的ts代码将会被编译为ES6版本的js代码
-
-
-
lib
-
指定代码运行时所包含的库(宿主环境)
-
可选值:
-
ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ……
-
-
示例:
-
"compilerOptions": { "target": "ES6", "lib": ["ES6", "DOM"], "outDir": "dist", "outFile": "dist/aa.js" }
-
-
-
module
-
设置编译后代码使用的模块化系统
-
可选值:
-
CommonJS、UMD、AMD、System、ES2020、ESNext、None
-
-
示例:
-
"compilerOptions": { "module": "CommonJS" }
-
-
-
outDir
-
编译后文件的所在目录
-
默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置
-
示例:
-
"compilerOptions": { "outDir": "dist" }
-
设置后编译后的js文件将会生成到dist目录
-
-
-
outFile
-
将所有的文件编译为一个js文件
-
默认会将所有的编写在全局作用域中的代码合并为一个js文件,如果module制定了None、System或AMD则会将模块一起合并到文件之中
-
示例:
-
"compilerOptions": { "outFile": "dist/app.js" }
-
-
-
rootDir
-
指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过rootDir可以手动指定根目录
-
示例:
-
"compilerOptions": { "rootDir": "./src" }
-
-
-
allowJs
-
是否对js文件编译
-
-
checkJs
-
是否对js文件进行检查
-
示例:
-
"compilerOptions": { "allowJs": true, "checkJs": true }
-
-
-
removeComments
-
是否删除注释
-
默认值:false
-
-
noEmit
-
不对代码进行编译
-
默认值:false
-
-
sourceMap
-
是否生成sourceMap
-
默认值:false
-
-
-
严格检查
-
strict
-
启用所有的严格检查,默认值为true,设置后相当于开启了所有的严格检查
-
-
alwaysStrict
-
总是以严格模式对代码进行编译
-
-
noImplicitAny
-
禁止隐式的any类型
-
-
noImplicitThis
-
禁止类型不明确的this
-
-
strictBindCallApply
-
严格检查bind、call和apply的参数列表
-
-
strictFunctionTypes
-
严格检查函数的类型
-
-
strictNullChecks
-
严格的空值检查
-
-
strictPropertyInitialization
-
严格检查属性是否初始化
-
-
-
额外检查
-
noFallthroughCasesInSwitch
-
检查switch语句包含正确的break
-
-
noImplicitReturns
-
检查函数没有隐式的返回值
-
-
noUnusedLocals
-
检查未使用的局部变量
-
-
noUnusedParameters
-
检查未使用的参数
-
-
-
高级
-
allowUnreachableCode
-
检查不可达代码
-
可选值:
-
true,忽略不可达代码
-
false,不可达代码将引起错误
-
-
-
noEmitOnError
-
有错误的情况下不进行编译
-
默认值:false
-
-
-
类型 | 例子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string |
‘hi’, “hi”,
|
任意字符串 |
boolean | true、false | 布尔值true或false |
any | * | 任意类型,不需要检查类型(没有类型检查就没有意义了,跟写JS一样。很不安全) |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 类型表示永远不会有值的一种类型。 |
object | {name:’孙悟空’} | 任意的JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple(元组) | [4,5] |
元组,TS新增类型,元组类型用来表示 已知元素数量和类型的数组 ,各元素的类型不必相同,对应位置的类型需要相同(赋值的顺序不能变) |
enum | enum{A, B} | 枚举,TS中新增类型 使用枚举类型可以为一组数值赋予友好的名字。枚举表示的是一个命名元素的集合值 |
number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
boolean
let isDone: boolean = false;
string
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;
any
在一些情况下,如果我们无法确定变量的类型时(或者无需确认类型时),我们可以将其指定为
any
类型。 TS中对于被标记为
any
类型的变量,是没有进行类型检查而直接通过编译阶段的检查。
在我们的系统中还是应当尽量避免使用
any
类型,以尽可能的保证系统健壮性。
let d: any = 4;
d = 'hello';
d = true;
unknown
TypeScript 3.0 引入了新的
unknown
类型,它是
any
类型对应的安全类型。
unknown
和
any
的主要区别是
unknown
类型会更加严格:
let demo:unknown="你好";
let demoa:unknown=1;
let demob:unknown=true;
let num:any=demo
// unknown 类型只能被赋值给 any 类型和 unknown 类型本身。
// 只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。
let numb:string=demo;//出错
void
没有返回值的函数,其返回值类型为
void
function fun(): void {
console.log("你好");
}
申明为
void
类型的变量,只能赋予
undefined
和
null
let unusable: void = undefined;
never
类型表示永远不会有值的一种类型。
object
let obj: object = {};
array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
tuple元组
已知数量与数据类型的数组
let x: [string, number];
x = ["hello", 10];
enum 枚举
TS中新增类型 使用枚举类型可以为一组数值赋予友好的名字。枚举表示的是一个命名元素的集合值
就是给一组数据起一个友好的名字
数字枚举类型和字符串枚举类型;
enum user{xiaoming,xiaohong,xiaobai}
console.log(user.xiaohong)//默认情况下数据的值从0开始
// 设置值
enum user{xiaoming,xiaohong=99,xiaobai}
console.log(user.xiaohong)
console.log(user.xiaobai)
// 字符串枚举设置值
enum user{xiaoming,xiaohong="小红",xiaobai="小白"}
console.log(user.xiaohong)
console.log(user.xiaobai)
类型别名
类型别名用来给一个类型起个新名字
,使用 type 创建类型别名,类型别名常用于联合类型。
在实际应用中,
有些类型名字比较长或者难以记忆,重新命名是一个较好的解决方案
。
// 创建一个String别名
type xiaoming=String;
// 使用别名
let textCon:xiaoming="xixi";
console.log(textCon);//xixi
联合类型-Union Type
联合类型表示的值可能是多种不同类型当中的某一个
。
联合类型放宽了类型的取值的范围,也就是说值的范围不再限于某个单一的数据类型
。
同时,它也不是无限制地放宽取值的范围,如果那样的话,完全可以使用 any 代替。
// 给多个类型创建一个名字
type newType=String|Number;
// 可以在赋值的时候赋值字符串与数字
let demoText:newType="你好我可以创建字符串与number"
面向对象
面向对象是程序中一个非常重要的思想,它被很多同学理解成了一个比较难,比较深奥的问题,其实不然。面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成。
-
举例来说:
-
操作浏览器要使用window对象
-
操作网页要使用document对象
-
操作控制台要使用console对象
-
一切操作都要通过对象,也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。
在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。
类(class)
要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。
-
定义类:
class 类名 {
属性名: 类型;
//constructor 方法是类的构造函数,是一个默认方法,通过 new 命令创建对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
示例:
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
使用类:
//通过new关键字可以方便的生产一个类的实例对象,这个生产对象的过程叫实例化
const p = new Person('孙悟空', 18);
p.sayHello();
面向对象的特点
-
封装
-
对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装
-
默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置
-
只读属性(readonly):
-
如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改
-
-
TS中属性具有三种修饰符:
-
public(默认值), 公开的,谁都能用 (默认public) 可以在类、子类和对象中修改
-
protected , 受保护的,仅仅类和类的子类能使用
-
private , 私有的,仅类自己里头才能使用
-
-
示例:
-
public
-
-
class a{
public name:String="xixi"
}
let demoa=new a()
// 因为name属性是使用public 谁都可以修改
demoa.name="haha"
console.log(demoa.name)
protected
class a{
protected name:String="xixi"
public showname(){
console.log("因为name是使用protected修饰的只能在当前类和类的子类中使用"+this.name)
}
}
let demoa=new a()
demoa.showname()
// 在外部是不能使用的
// console.log(demoa.name)
private
class a{
private name:String="xixi"
public showname(){
console.log("因为name是使用private仅类自己里头才能使用"+this.name)
}
}
let demoa=new a()
demoa.showname()
// 在外部是不能使用的
// console.log(demoa.name)
静态属性
– 静态属性(方法),也称为类属性。**使用静态属性无需创建实例,通过类即可直接使用**
– 静态属性(方法)使用static开头
– 示例:
class Tools{
static PI:Number = 3.1415926;
static sum(num1: number, num2: number){
return num1 + num2
}
}
console.log(Tools.PI);
console.log(Tools.sum(123, 456));
继承
-
继承时面向对象中的又一个特性
-
通过继承可以将其他类中的属性和方法引入到当前类中
-
示例:
-
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
-
通过继承可以在不修改类的情况下完成对类的扩展
-
重写
-
发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
-
示例:
-
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
run(){
console.log(`父类中的run方法!`);
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
run(){
console.log(`子类中的run方法,会重写父类中的run方法!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
-
在子类中可以使用super来完成对父类的引用
super
class A {}
class B extends A {
constructor() {
super(); // ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。在 constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B
console.log(this)
}
}
接口(Interface)
接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用
使用interface关键字定义 接口一般首字母大写 有的编程语言中会建议接口的名称加上 I 前缀
-
示例(检查对象类型):
interface IUser{
name:String,
showname():void
}
-
示例(实现)
-
接口使用:使用:号接口名来进行使用 注意:定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的。赋值的时候,变量的形状必须和接口的形状保持一致
interface IUser{
name:String,
showname():void
}
let user:IUser={
name:"xixi",
showname(){
console.log(`名字是${this.name}`)
}
}
console.log(user.name)
user.showname()
可选属性
可选属性:可选属性的含义是该属性可以不存在 有时候不要完全匹配一个接口,那么可以用可选属性。使用?号
interface IUser{
name?:String,
showname():void
}
let user:IUser={
// 因为name是可选属性所以可以不用定义
showname(){
console.log(`名字是${this.name}`)
}
}
console.log(user.name)
user.showname()
泛型(Generic)
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
-
举个例子:
function test(arg: any): any{
return arg;
}
-
上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型
-
类中使用泛型:
class MyClass<T>{
prop: T;
constructor(prop: T){
this.prop = prop;
}
}
这里的
<T>
就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在使用T来表示该类型。所以泛型其实很好理解,
就表示某个类型
。
HOOK
useState()就是React提供最基础、最常用的Hook,主要用来定义和管理本地状态。
useState返回的是一个数组(长度为2),数组的第一个对象表示当前状态的值,第二个对象表示用于更改状态的函数,类似于类组件的setState。
let [val(当前状态的值),setVal(改变状态的函数)]=useState(当前状态的初始值)
import {useState} from "react"
let Home:React.FC<{}>=()=>{
// 创建状态
let [text,setText]=useState("你好")
return (
<div>
使用useState数据---{text}
</div>
)
}
export default Home
修改
import {useState} from "react"
let Home:React.FC<{}>=()=>{
// 创建状态
let [text,setText]=useState("你好")
let fun=():void=>{
// 修改状态
setText(text="你坏")
}
return (
<div>
使用useState数据---{text}
<button onClick={fun}>点我修改</button>
</div>
)
}
export default Home
多个状态怎么办
1.声明对象类型的状态
let Home:React.FC<{}>=()=>{
// 创建对象类型状态
let [text,setText]=useState({
a:"第1",
b:"第2",
c:"第3",
})
return (
<div>
使用useState数据---{text.a}----{text.b}---{text.c}
</div>
)
}
export default Home
修改
import {useState} from "react"
let Home:React.FC<{}>=()=>{
// 创建对象类型状态
let [text,setText]=useState({
a:"第1",
b:"第2",
c:"第3",
})
let fun=():void=>{
// 修改状态
setText({...text,a:"我变了"})
}
return (
<div>
使用useState数据---{text.a}----{text.b}---{text.c}
<button onClick={fun}>点我修改a</button>
</div>
)
}
export default Home
2.多次声明
按照传统的方式写多次即可
useRef
hooks中可以通过 useRef()获取Dom节点
import {useRef} from "react"
let Home:React.FC<{}>=()=>{
let u:any=useRef(null)
let fun=():void=>{
u.current.style.color="red"
}
return (
<div>
<p ref={u}>修改我的颜色</p>
<button onClick={fun}>点我修改a</button>
</div>
)
}
export default Home
useEffect
useEffect:函数组件中没有生命周期,那么可以使用 useEffect 来替代。可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
import {useEffect} from "react"
let Home:React.FC<{}>=()=>{
useEffect(()=>{
console.log("执行了");
})
return (
<div>
<p>执行了</p>
</div>
)
}
export default Home
## useReducer
useReducer和redux没有关系。它主要就是修改数据的 他就是加强版的useState在处理多数据修改的时候降低复杂度
创建数据
import {useReducer} from "react"
let Home:React.FC<{}>=()=>{
let reducer:any=(state:Number,action:Object):Number=>{
return state
}
// 1.初始化数据
// state代表数据
// dispatch代表修改动作
// reducer修改数据的任务
// 1 是初始化数据
let [state,dispatch]=useReducer(reducer,0)
return (
<div>
<p>我是组件--{state}</p>
</div>
)
}
export default Home
修改数据
import {useReducer} from "react"
let Home:React.FC<{}>=()=>{
let reducer:any=(state:any,action:any):Number=>{
switch (action.type) {
case "ADD":
return state+1
break;
default:
break;
}
return state
}
// 1.初始化数据
// state代表数据
// dispatch代表修改动作
// reducer修改数据的任务
// 1 是初始化数据
let [state,dispatch]:any=useReducer(reducer,1)
let fun=():void=>{
dispatch({type:"ADD"})
}
return (
<div>
<p>我是组件--{state}</p>
<button onClick={fun}>+1</button>
</div>
)
}
export default Home
虚拟DOM
React的高性能体现就是虚拟dom
浏览器的出现是为了展示图片文字等内容给用户 但是随着技术的发展浏览器的功能性已经和设计之初完全不同 浏览器上现在需要有更多的特效 游戏的运行等大型复杂的功能接踵而至
但是慢慢的开发这觉得浏览器的运行效率随着程序的复杂度提高性能 效率越来越低 **原因很大一部分就是大量的DOM操作影响了他的速度**
传统的
比如在传统的js或者jquery中 有一个div 我们想把其中的内容从xixi变成haha 情况是如下的:
document.getElementById("元素").innerHTML=??
$("元素").html(??)
那么从上面的代码大家会发现 浏览器如果要修改一个很简单的内容都需要进行一次dom操作 如果要修改很多个就会源源不断的DOM修改进行下去
react中
react就对上面大量的dom操作进行了修改和优化
具体实现
react会把浏览器上渲染的dom(
真实dom
)转换成一个 js对象(
这个对象称之为
虚拟dom
virtualDOM
)
在拿着虚拟dom和需要修改的新内容进行比较 发现出需要修改的地方进行修改
react高性能的原理
在Web开发中我们需要把数据的变化反映到页面ui中 这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。
React为此引入了虚拟DOM (virtual DoM) 的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DON构造都是通过虛拟DOM进行,每当数据变化时,React都会重新构建整个DON树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虛拟DOM的刷新,在一个事件循环(Event Loop) 内的两次数据变化会被合并
例如你连续的先将节点内容从A-B,B-A,React会认为A变成B,然后又从B变成A UI不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。
尽管每一次都需要构造完整的虛拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,因而能达到提高性能的目的。这样在保证性能的同时,开发者将不再需要关心某个数据变化如何更新到一个或多个具体的DOM元素,而只需要关心在任意一个数据状态下,整个界面是如何Render的。
React Fiber
在react 16之后发布的一种react 核心算法,
React Fiber是对核心算法的一次重新实现(官网说法)
。之前用的是diff算法。在之前React中,更新过程是同步的,这可能会导致性能问题。当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期西数,计算和比对virtualDOM,最后更新DOM树,这整个
过程是同步进行
的,也就是说只要一个加载或者更新过程开始,中途不会中断。 因为Javascript单线程的特点,如果组件树很大的时候,每个同步任务耗时太长,就会出现卡顿。
React Fiber的方法其实很简单-
-
分片。把一个耗时长的任务分成很多个小片,每一个小片的运行时问很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。