是唯一的,因为它仅仅只会渲染一个路径,当它匹配完一个路径后,就会停止渲染了。
```
```jsx
import React, { Component } from 'react'
import {Route,Link,NavLink,Switch} from "react-router-dom"
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import Shop from "../views/shop.jsx"
import No from "../views/no.jsx"
export default class index extends Component {
render() {
return (
点我去home
点我去phone
点我去shop
{/* 404路由规则的最下面 */}
)
}
}
```
## Redirect 重定向
```
导入Redirect
import {Redirect } from 'react-router-dom'
定义重定向路径
```
```jsx
import React, { Component } from 'react'
import {Route,Link,NavLink,Switch,Redirect} from "react-router-dom"
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import Shop from "../views/shop.jsx"
import No from "../views/no.jsx"
export default class index extends Component {
render() {
return (
点我去home
点我去phone
点我去shop
{/* 设置路由重定向 */}
{/* 404路由规则的最下面 */}
)
}
}
```
# 路由传参
## params方式
### 1-在 Router标签后面拼接传递参数的名字
```jsx
import React, { Component } from 'react'
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import No from "../views/no.jsx"
import {Route,Redirect,Switch,Link} from "react-router-dom"
export default class index extends Component {
render() {
return (
{/* 2-设置发送的数据 */}
点我去user并且传递参数
{/* 1-在 Router标签后面拼接传递参数的名字 */}
)
}
}
```
### 2-设置发送的数据
```jsx
import React, { Component } from 'react'
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import No from "../views/no.jsx"
import {Route,Redirect,Switch,Link} from "react-router-dom"
export default class index extends Component {
render() {
return (
{/* 2-设置发送的数据 */}
点我去user并且传递参数
{/* 1-在 Router标签后面拼接传递参数的名字 */}
)
}
}
```
### 3-在需要接收的路由组件中接收this.props.match.params.name
```jsx
import React, { Component } from 'react'
export default class user extends Component {
render() {
return (
{/* 3-在需要接收的路由组件中接收this.props.match.params.name */}
我是user---{this.props.match.params.xiaoming}
)
}
}
```
### 传参params方式优缺点
```
优势 : 刷新地址栏,参数依然存在
缺点 : 只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
```
## state方式
### 1-在Link中设置发送的数据
```
点我去d
```
```jsx
import React, { Component } from 'react'
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import No from "../views/no.jsx"
import {Route,Redirect,Switch,Link} from "react-router-dom"
export default class index extends Component {
render() {
return (
{/*在Link中设置发送的数据 */}
点我去phone state传参
)
}
}
```
### 2-在需要接收的路由组件中接收
```
this.props.location. state.XXX
```
```jsx
import React, { Component } from 'react'
export default class phone extends Component {
render() {
return (
我是phone---{this.props.location.state.xiaohong}
)
}
}
```
### state 优缺点
```
优势:传参优雅地址栏不显示传递的数据,传递参数可传对象;
缺点:刷新地址栏,参数丢失
```
# 路由render渲染
## 基本写法
```
修改Route 里面的组件调用方式为:
render={(props)=>{return <组件/>}}
render调用一个函数那么我们就可以决定什么时候渲染他
同时传入props那么就可以在路由组件中使用history: {…}, location: {…}, match: {…}这几个对象
```
```jsx
//这是一个测试组件renderdemo
import React, { Component } from 'react'
export default class renderdemo extends Component {
render() {
return (
我是用来测试render渲染的组件
可以让路由在渲染之间 先进一重判断
)
}
}
```
```jsx
import React, { Component } from 'react'
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import No from "../views/no.jsx"
import Renderdemo from "../views/renderdemo.jsx"
import {Route,Redirect,Switch,Link} from "react-router-dom"
export default class index extends Component {
render() {
return (
{/*路由render渲染*/}
{return true? : }}>
)
}
}
```
## 路由render渲染写法传递数据
### 1-只需要向组件内部传递即可 正向传值
```
{return true? : }}>
```
### 2-取值--- this.props.xxx
```
this.props.text
```
# 性能优化
## 小案例-点击变色
### 父组件
```jsx
import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
constructor(props){
super(props)
this.state={
arr:[
{title:"标题1",style:false},
{title:"标题2",style:false},
{title:"标题3",style:false},
{title:"标题4",style:false},
{title:"标题5",style:false},
{title:"标题6",style:false}
]
}
}
fun=(val)=>{
console.log(val)
let newarr=this.state.arr;
newarr[val].style=!newarr[val].style
this.setState({
arr:newarr
})
}
render() {
return (
{
this.state.arr.map((v,i)=>{
return (
)
})
}
)
}
}
```
### 子组件
```jsx
import React, { Component } from 'react'
export default class zi extends Component {
render() {
console.log("我是子组件")
let {text,style,num}=this.props
return (
{text}
)
}
}
```
### 问题
```
在子组件的render中添加一个输出 在运行的时候发现每次运行的时候子组件都被调用了多次
那么发现当前我们写的有严重的性能问题;
原因是因为 我们在更新数据的时候使用setState修改整个数据 那么数据变了 便利的时候所有内容都要被重新渲染。
数量少没有关系 如果便利出来的数据很多 那么就会严重影响我们的性能
```
## 提高性能--方案1
```
解决方式1 使用生命周期的:
shouldComponentUpdate(nextProps最新的props,nextState最新的state) 判定组件是否要更新
```
```jsx
import React, { Component } from 'react'
export default class zi extends Component {
shouldComponentUpdate(nextProps){
return nextProps.style!=this.props.style
}
render() {
console.log("我是子组件")
let {text,style,num}=this.props
return (
{text}
)
}
}
```
## 提高性能--方案2
```
解决方式2 纯组件(PureComponent)--类组件中使用
PureComponent是优化react应用程序最重要的方法,组件发生更新时,组件的props和state没有改变,render方法就不会触发 .可以减少不必要的render次数,提高性能。
省去虚拟dom生成和对比的过程,其实就是react自动帮忙做了一个浅比较(它只比较props和state的内存地址,如果内存地址相同,则shouldComponentUpdate生命周期就返回false。)
```
```jsx
import React, { Component,PureComponent } from 'react'
export default class zi extends PureComponent {
render() {
console.log("我是子组件")
let {text,style,num}=this.props
return (
{text}
)
}
}
```
## 提高性能--方案3
```
解决方式3 React.memo() -- 无状态组件中使用
类似于PureComponent,不同于React.memo是函数/无状态组件,React.PureComponent是类组件。memo依然是一种对象的浅比较
```
# 高阶组件HOC
- 在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用。可以复用在react组件中的代码与逻辑
- HOC--参数是组件同时返回值也是组件 这类组件我们叫做高阶组件(HOC)
- 高阶组件的本质是高阶函数 比如js中的 map() filter()forEach......
- 高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。
- 例如封装一个功能在很多个组件中都想使用 就是每个组件都带一个版权信息 u
## 基本使用
### 封装一个基本高阶组件
```jsx
import React, { Component } from 'react'
let hoc=(Com)=>{//因为形参接收的是一个组件 所以首字母必须大写
return class index extends Component {
render() {
return (
我是高阶组件的内容
)
}
}
}
export default hoc
```
### 使用
```jsx
import React, { Component } from 'react'
import Topbar from "../components/topbar.jsx"
//引入高阶组件
import hoc from "../hoc"
//修改暴漏
class home extends Component {
render() {
return (
首页
)
}
}
//利用高阶组件暴漏
export default hoc(home)
```
## 高阶组件HOC接受props
```
如果组件是被高阶组件导出的 那么在正向传值的时候需要在高阶组件中进行传递
如果组件正常调用了高阶组件 那么他的父组件就没有办法正常传递props参数
我们需要在高阶组件中 使用{...this.props} 对参数进行传递设置
```
```jsx
import React, { Component } from 'react'
let hoc=(Com)=>{
return class index extends Component {
render() {
return (
我是高阶组件的内容
)
}
}
}
export default hoc
```
## 反向继承
```
本质就是HOC高阶组件的 条件渲染渲染劫持 根据我们传递过来的参数的不同来渲染不同的组件内容
反向继承最核心作用,是渲染劫持(拦截了渲染可以让我们进行条件渲染)
super.render()是调用父类的render()渲染
在使用高阶组件,暴漏组件的时候需要传递参数
```
```jsx
//反向继承高阶组件
import React, { Component } from 'react'
let hocb=(Com,num)=>{
//名字要改成第一个形参的名字,不能使用Component
return class indexb extends Com {
render() {
if(num<10){
return (
参数小于10了
)
}else{
return (
{/*渲染当前这个高阶组件的内容*/}
)
}
}
}
}
export default hocb
```
```jsx
import React, { Component } from 'react'
import hocb from "../hoc/indexb.js"
class hocdemob extends Component {
render() {
return (
我是用来测试高阶组件的反向继承的组件
)
}
}
//在使用高阶组件,暴漏组件的时候需要传递参数
export default hocb(hocdemob,11)
```
```jsx
import React, { Component } from 'react'
import Topbar from "../components/topbar.jsx"
import Hocdemob from "../components/hocdemob.jsx"
export default class phone extends Component {
render() {
return (
{/*调用使用高阶组件暴漏的组件*/}
手机
)
}
}
```
# redux
## redux是什么
```
Redux是为javascript应用程序提供一个状态管理工具
集中的管理react中多个组件的状态
redux是专门作状态管理的js库(不是react插件库可以用在其他js框架中例如vue,但是基本用在react中)
```
## 什么时候用redux
```
需求场景:
某个组件的状态需要共享的时候
某个组件的状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
```
## redux三大原则
- 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
- State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers(一些纯函数,它接收先前的 state 和 action,)
## redux常用概念
- Store:管理着整个应用的状态,可以通过getState()来重新获得最新的状态(state)。
- Action:是唯一可以改变状态(state)的方式,服务器的各种推送、用户自己做的一些操作,最终都会转换成一个个的Action,而且这些Action就是修改的动作,可以通过dispatch()方法来进行调用
- Reducer:Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。(纯函数就是只要传入相同的参数,每次都应返回相同的结果)
## redux常用方法
```
createStore()作用:创建一个Redux store来存放应用中所有的state,
一个应用只能有个store。函数返回store对象。
getState()作用:获取数据
dispatch()分发action,这是改变state的唯一方法。
subscribe()添加一个变化监听器,每当改变store的时候就会执行
```
## redux基本使用
### 1-下载创建
```
下载:npm install --save redux
建议:在项目中创建一个sotre文件夹,用来保存与redux相关的内容
```
### 2-在sotre中创建index.js
```js
// 1.使用redux,先创建store对象 先引用
import {createStore} from "redux"
// 5.创建数据
let xiaoming={
name:"xixi",
age:18
}
// 4.创建redecer reducer是一个纯函数
// state数据源 actions 等同于vuex的mustations就是修改的动作
let reducer=(state=xiaoming,actions)=>{
// 6return state数据
return state
}
// 2.创建store对象 3.向store对象中传递reducer
let store=createStore(reducer)
// 3.暴漏
export default store
```
### 3-•在src下的index.js中使用
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './router';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom"
//使用store
import store from "./store"
console.log(store.getState().name)
ReactDOM.render(
,
document.getElementById('root')
);
reportWebVitals();
```
### 4-在组件中使用
```jsx
import React, { Component } from 'react'
import Topbar from "../components/topbar.jsx"
import hoc from "../hoc"
//引用store
import store from "../store/index"
class home extends Component {
constructor(props) {
super(props)
this.state = {
//读取数据并赋值给state
text: store.getState().age
}
}
render() {
return (
首页---{this.state.text}
)
}
}
export default hoc(home)
```
## redux修改操作
### 1-在reducer中添加action任务
```jsx
// 1.使用redux,先创建store对象 先引用
import {createStore} from "redux"
// 5.创建数据
let xiaoming={
name:"xixi",
age:18
}
// 4.创建redecer reducer是一个纯函数
// state数据源 actions 等同于vuex的mustations就是修改的动作
let reducer=(state=xiaoming,actions)=>{
// 6return state数据
// return state
//修改
//actions.type 修改的任务名
//type是从页面传过来的名字,用actions打点接收
switch (actions.type) {
case "ADD":
console.log({...state,age:state.age+1})
//es6扩展运算符
return {...state,age:state.age+1}
break;
case "DEL":
return {...state,age:state.age-1}
break;
default:
return state
break;
}
}
// 2.创建store对象 3.向store对象中传递reducer
let store=createStore(reducer)
// 3.暴漏
export default store
```
### 2-dispatch()方法来进行调用修改动作 /subscribe() 监听数据变化
```
dispatch()方法来进行调用修改动作后
在页面点击按钮后,数据并没有修改(constructor是在初始化自动执行,redux数据改变了 但是组件并没有重新渲染)
subscribe()订阅一个变化监听器,每当改变store的时候就会执行
```
```jsx
import React, { Component } from 'react'
import Topbar from "../components/topbar.jsx"
import hoc from "../hoc"
import store from "../store/index"
class home extends Component {
constructor(props) {
super(props)
this.state = {
text: store.getState().age
}
store.subscribe(()=>{
this.setState({
text:store.getState().age
})
})
}
add=()=>{
// 派发动作的名字在store中,可以用actions打点 接收
store.dispatch({type:"ADD"})
}
del=()=>{
store.dispatch({type:"DEL"})
}
render() {
return (
首页---{this.state.text}
点我+1
点我-1
)
}
}
export default hoc(home)
```
## 使用actionCreator统一创建action
### 没有分派前
#### store的index.js
```js
// 1 引用
import {createStore} from "redux"
// 6 创建数据
let data={
num:9527
}
// 5 创建reducer 7注入数据
let reducer=(state=data,actions)=>{
// return state
//type是从页面传过来的,用actions打点接收
switch (actions.type) {
case "NUM_DATA_ADD":
// number是从页面传过来的,用actions打点接收
return {...state,num:state.num+actions.number}
break;
case "NUM_DATA_DEL":
return {...state,num:state.num-actions.number}
break;
default:
return state
break;
}
}
// 2 创建store对象 4 创建数据与修改动作
let store = createStore(reducer)
// 3 暴漏
export default store
```
#### 页面引用
```jsx
import React, { Component } from 'react'
import store from "../store"
export default class phone extends Component {
constructor(props){
super(props)
this.state={
phonenum:store.getState().num
}
store.subscribe(()=>{
this.setState({
phonenum:store.getState().num
})
})
}
add=()=>{
// 派发动作的名字在store中,可以用actions打点 接收
store.dispatch({type:"NUM_DATA_ADD",number:10})
}
del=()=>{
store.dispatch({type:"NUM_DATA_DEL",number:20})
}
render() {
return (
手机---{this.state.phonenum}
+1
-1
)
}
}
```
```
今后会有很多的修改动作都直接在组件中的store.dispatch()创建,当项目比较复杂的时候会比较难以管理。
redux希望使用专门的工厂函数来创建action
```
### 封装派发动作
```
把原来写在redux文件中的修改动作分离出去独立管理
```
#### 1-创建一个js文件
封装派发动作
```js
// 用来存储派发动作
export let addaction=(number)=>{
return {type:"NUM_DATA_ADD",number}
}
export let delaction=(number)=>{
return {type:"NUM_DATA_DEL",number}
}
```
#### 2-在页面引用
```jsx
import React, { Component } from 'react'
import store from "../store"
//引用封装派发动作的文件
import {addaction,delaction} from "../store/actionCreator.js"
export default class phone extends Component {
constructor(props){
super(props)
this.state={
phonenum:store.getState().num
}
store.subscribe(()=>{
this.setState({
phonenum:store.getState().num
})
})
}
add=()=>{
// 派发动作的名字在store的actions中可以接收
store.dispatch(addaction(2))
}
del=()=>{
store.dispatch(delaction(10))
}
render() {
return (
手机---{this.state.phonenum}
+1
-1
)
}
}
```
### 封装派发动作名(action任务名)
```
在actionCreator.js与reducer中出现了两次type名 为了方便管理 我们对任务名也进行修改
```
#### 1-创建一个js文件
封装任务名
```js
// 封装任务名
export const NUM_DATA_ADD="NUM_DATA_ADDA"
export const NUM_DATA_DEL="NUM_DATA_DELA"
```
#### 2-在需要的地方使用
```js
//在封装派发动作的文件中引用
import {NUM_DATA_ADD,NUM_DATA_DEL} from "./actionType.js"
// 用来存储我们的派发动作
export let addaction=(number)=>{
return {type:NUM_DATA_ADD,number}
}
export let delaction=(number)=>{
return {type:NUM_DATA_DEL,number}
}
```
```jsx
// 1 引用
import {createStore} from "redux"
// 在store中使用
import {NUM_DATA_ADD,NUM_DATA_DEL} from "./actionType.js"
// 6 创建数据
let data={
num:9527
}
// 5 创建reducer 7注入数据
let reducer=(state=data,actions)=>{
// return state
// actions.type是从页面传过来的
switch (actions.type) {
case NUM_DATA_ADD:
// actions.number是从页面传过来的
return {...state,num:state.num+actions.number}
break;
case NUM_DATA_DEL:
return {...state,num:state.num-actions.number}
break;
default:
return state
break;
}
}
// 2 创建store对象 4 创建数据与修改动作
let store = createStore(reducer)
// 3 暴漏
export default store
```
### 封装reducer模块
```
今后当项目体积逐渐变大会出现一种情况。就是所有的操作全部写在一个reducer中,非常难以管理。那么在这个时候可以把组件自己的reducer单独剥离出来进行管理。
```
#### 1-封装reducer模块的内容
```
创建一个reducer文件夹,存放不同的reducer模块文件,
把原本在store文件夹中reducer里面的内容放到刚创建的reducer文件夹的文件中
```
```js
import {NUM_DATA_ADD,NUM_DATA_DEL} from "../actionType.js"
let data={
num:9527,name:"xixi"
}
let phonereducer=(state=data,actions)=>{
switch (actions.type) {
case NUM_DATA_ADD:
return {...state,num:state.num+actions.number}
break;
case NUM_DATA_DEL:
return {...state,num:state.num-actions.number}
break;
default:
return state
break;
}
return state
}
export default phonereducer
```
#### 2-合并 reducer
```js
// 用来合并每个组件的reducer
import phonereducer from "./reducers/phonereducer.js"
import {combineReducers} from "redux"
let reducer=combineReducers({
//拆分的reducers模块名
phonereducer
})
export default reducer
```
#### 3-在index.js中引入合并后的reducer
```js
// 1 引用
import {createStore} from "redux"
import reducer from "./reducer.js"
// import {NUM_DATA_ADD,NUM_DATA_DEL} from "./actionType.js"
// // 6 创建数据
// let data={
// num:9527
// }
// // 5 创建reducer 7注入数据
// let reducer=(state=data,actions)=>{
// // return state
// // actions.type是从页面传过来的
// switch (actions.type) {
// case NUM_DATA_ADD:
// // actions.number是从页面传过来的
// return {...state,num:state.num+actions.number}
// break;
// case NUM_DATA_DEL:
// return {...state,num:state.num-actions.number}
// break;
// default:
// return state
// break;
// }
// }
// 2 创建store对象 4 创建数据与修改动作
let store = createStore(reducer)
// 3 暴漏
export default store
```
#### 4-在页面组件使用要加模块名
```jsx
import React, { Component } from 'react'
import store from "../store"
import {addaction,delaction} from "../store/actionCreator.js"
export default class phone extends Component {
constructor(props){
super(props)
this.state={
//使用数据要加模块名
phonenum:store.getState().phonereducer.num
}
store.subscribe(()=>{
this.setState({
//使用数据要加模块名
phonenum:store.getState().phonereducer.num
})
})
}
add=()=>{
// 派发动作的名字在store的actions中可以接收
store.dispatch(addaction(2))
}
del=()=>{
store.dispatch(delaction(10))
}
render() {
return (
手机---{this.state.phonenum}
+1
-1
)
}
}
```
# 闭环请求
## 数据
```
使用json-server模拟数据
在命令行输入 json-server --watch json的名字 --port 4000(端口号) 进行启动
```
```js
{
"user":[
{"name":"xixi1","age":181},
{"name":"xixi2","age":182},
{"name":"xixi3","age":183},
{"name":"xixi4","age":184},
{"name":"xixi5","age":185}
]
}
```
## 网络请求模块
### 拦截器-service .js
```js
import axios from "axios"
// 创建axios 赋值给常量service
const service = axios.create();
// 添加请求拦截器(Interceptors)
service.interceptors.request.use(function (config) {
// 发送请求之前做写什么
return config;
}, function (error) {
// 请求错误的时候做些什么
return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default service
```
### 请求模块-getlink
```js
import service from "../util/service.js"
export function getlink(url){
return new Promise((resolve,reject)=>{
service.request({
url,
method:"get"
}).then((ok)=>{
resolve(ok)
}).catch((err)=>{
reject(err)
})
})
}
```
## reducers模块
### 1-派发动作名文件--actionType.js
```js
export const DATALIST_ARR_USER="DATALIST_ARR_USER"
```
### 2-派发动作文件--actionCreator.js
```js
import {DATALIST_ARR_USER} from "./actionType.js"
export const updata=(data)=>{
return {type:DATALIST_ARR_USER,data}
}
```
### 3-封装reducer模块--homereducer.js
```js
import {DATALIST_ARR_USER} from "../actionType.js"
let data={
arr:[]
}
export let homereducer=(state=data,actions)=>{
switch (actions.type) {
case DATALIST_ARR_USER:
return {...state,arr:actions.data}
break;
default:
break;
}
return state
}
```
### 4-合并封装的reducer模块文件--reducer.js
```js
// 合并reducer的
// 引用合并的文件进来
import {homereducer} from "./reducers/homereducer.js"
import {combineReducers} from "redux"
// 开始合并
export let reducer=combineReducers({
homereducer
})
```
### 5-主文件--index.js
```js
import {createStore} from "redux"
import {reducer} from "./reducer.js"
let store=createStore(reducer)
export default store
```
## 组件中发送请求
```jsx
import React, { Component } from 'react'
import {getlink} from "../api/getapi.js"
import store from "../store"
import {updata} from "../store/actionCreator.js"
export default class home extends Component {
constructor(props){
super(props)
this.state={
arr:store.getState().homereducer.arr
}
}
componentDidMount() {
store.subscribe(()=>{
this.setState({
arr:store.getState().homereducer.arr
})
})
getlink("http://localhost:4000/user").then((ok)=>{
console.log(ok)
{/*把请求到的文件发送到reducer中*/}
store.dispatch(updata(ok.data))
})
}
render() {
return (
我需要从模拟数据里面请求数据 然后传递给redux中 并且最终展示在页面里
{
this.state.arr.map((v,i)=>{
return (
{v.name}-----{v.age}
)
})
}
)
}
}
```
# react-redux
```
redux与react的耦合度过于高。代码不够简洁(组件中出现了大量的store对象)
一个react的插件
专门用来简化react应用中使用redux
```
## 下载
```
npm install --save react-redux
cnpm install --save react-redux
```
## react-redux常用概念
```
组件:把 store 提供给其子组件
connect 高阶组件:链接 链接react组件和redux(组件状态要从redux中获取)
```
## react-redux使用
### 1--在index.js中 创建Provider
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/oneshop/fu.jsx';
import reportWebVitals from './reportWebVitals';
//引用"react-redux"
import {Provider} from "react-redux"
//引用store对象
import store from "./store"
ReactDOM.render(
//有路由包裹在路由外面,没路由直接替换
//第一个store是属性,第二个是引入的
,
document.getElementById('root')
);
reportWebVitals();
```
### 2--在需要使用数据的组件中引入
```jsx
import React, { Component } from 'react'
// connect是一个高阶组件
import {connect} from "react-redux"
export default class phone extends Component {
render() {
return (
我是使用react-redux来进行数据的读取
)
}
}
```
### 3--修改暴漏
```
因为connect是一个高阶组件,所以用高阶组件的方式来暴漏
```
```jsx
import React, { Component } from 'react'
// connect是一个高阶组件
import {connect} from "react-redux"
class phone extends Component {
render() {
return (
我是使用react-redux来进行数据的读取
)
}
}
// 有两个(),因为connect是一个方法 当这个方法被调用的时候返回的才是一个高阶组件
export default connect()(phone)
```
### 4--向connect里面传递数据
```
生成数据在props中
形参数是state的数据 当前函数必须return一个对象
```
```jsx
import React, { Component } from 'react'
// connect是一个高阶组件
import {connect} from "react-redux"
class phone extends Component {
render() {
return (
我是使用react-redux来进行数据的读取
)
}
}
// 为什么要有两个()
// connect是一个方法 当这个方法被调用的时候返回的才是一个高阶组件
export default connect(
// 传递数据
state=>({state})
)(phone)
```
### 5--使用数据
```jsx
{this.props.state.xxx}
{this.props.state.reducer模块名.age}
```
```jsx
import React, { Component } from 'react'
// connect是一个高阶组件
import {connect} from "react-redux"
class phone extends Component {
render() {
return (
我是使用react-redux来进行数据的读取
{this.props.state.homereducer.age}
)
}
}
// 为什么要有两个()
// connect是一个方法 当这个方法被调用的时候返回的才是一个高阶组件
export default connect(state=>({state}))(phone)
```
# react-redux 修改数据
## store文件夹中
### 1-派发动作名文件--actionType.js
```js
export const ADD_LIST_REDUX="ADD_LIST_REDUX"
```
### 2--派发动作文件--actionCreator.js
```js
import {ADD_LIST_REDUX} from "./actionType.js"
export function add(num){
return {type:ADD_LIST_REDUX,num}
}
```
### 3--封装reducer模块--homereducer.js
```js
import {ADD_LIST_REDUX} from "../actionType.js"
// 中多个reducer的模块其中一个
let data={
name:"xixi",
age:18
}
export let homereducer=(state=data,action)=>{
switch (action.type) {
case ADD_LIST_REDUX:
return {...state,age:state.age+action.num}
break;
default:
return state
break;
}
}
```
### 4-合并封装的reducer模块文件--reducer.js
```js
// 引用所有的reducer模块
import {homereducer} from "./reducers/homereducer.js"
// 引用合并reducer的功能
import {combineReducers} from "redux"
let reducer = combineReducers({
homereducer
})
export default reducer
```
### 5-主文件--index.js
```js
import {createStore} from "redux"
import reducer from "./reducer.js"
let store=createStore(reducer)
export default store
```
## 组件内修改数据
### 组件中修改
```
this.props.dispatch(派发动作)
```
```jsx
import React, { Component } from 'react'
// connect是一个高阶组件
import {connect} from "react-redux"
//引入派发动作
import {add} from "../store/actionCreator.js"
class phone extends Component {
add=()=>{
this.props.dispatch(add(2))
}
render() {
return (
我是使用react-redux来进行数据的读取
{this.props.state.homereducer.age}
点我+2
)
}
}
// 为什么要有两个()
// connect是一个方法 当这个方法被调用的时候返回的才是一个高阶组件
export default connect(state=>({state}))(phone)
```
# immutable不可变对象
Facebook 工程师使用3年时间打造,与React同期出现,但是没有被默认放到React工具集中,它内部实现了一套完整的数据持久化 里面有很多常见的数据类型Collection List Map Set等
## 主要有三种数据结构
```
Map: 键值对集合,对应于Object Es6中也有专门的Map对象
List: 有序可以重复的列表,对应于Array
set: 无序并且不可重复key的数组
```
## 作用
```
主要完成的就是数据的持久化
```
## immutable原理
```
Immutable 实现的原理是(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有节点都复制一遍带来的性能损耗,Immutable 使用了(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
```
## immutable介绍
```
Immutable Data就是一但创建,就不能在被改变的数据,对于immutabe对象的任何修改或者添加删除操作都会返回一个新的immutable对象
为什么每次都要返回一个新的immutable对象呢?
因为redux中数据是只读的 如果任意一个使用的位置都可以直接修改redux中的数据 那么可能会影响其他位置引用的内容,造成现实错误
immutable对象一旦发生了改变 就会返回一个新的immutable对象
```
## 下载
```
npm install --save immutable
cnpm install --save immutable
```
## 传统对象修改
```html
```
## immutable-Map
```
键值对集合
```
### 创建map对象
```js
let {Map}=require("immutable")
// Map格式对应的是传统的obj对象
// 1.创建
let demo=Map({
name:"xixi",
age:18
})
```
### 读取get()
```js
let {Map}=require("immutable")
// Map格式对应的是传统的obj对象
// 1.创建
let demo=Map({
name:"xixi",
age:18
})
// 读取要使用get()方法进行读取
console.log(demo.get("name"))
```
### 修改set()
```js
let {Map}=require("immutable")
let demo=Map({
name:"xixi",
age:18
})
// 修改 使用set(你要修改的key,修改的值)
// immutable对象一旦发生了改变 就会返回一个新的immutable对象
let newdemo=demo.set("name","haha")
console.log(demo.get("name")+"-----"+newdemo.get("name"))
```
### 删除一个delete ()
```js
let {Map}=require("immutable")
let demo=Map({
name:"xixi",
age:18
})
//只能删除一个
let newdemo=demo.delete("name")
console.log(newdemo.get("name"))
```
### 删除多个deleteAll ()
```js
let {Map}=require("immutable")
let demo=Map({
name:"xixi",
age:18
})
// 删除多个
let newdemo=demo.deleteAll(["name","age"])
console.log(newdemo.get("age"))
```
### 清空clear()
```js
let {Map}=require("immutable")
let demo=Map({
name:"xixi",
age:18
})
let newdemo=demo.clear()
```
### 合并merge()
```js
let {Map}=require("immutable")
let a=Map({
name:"黄璜"
})
let b=Map({
sex:"男"
})
let newdemo=a.merge(b)
console.log(newdemo.get("sex"))
```
### 转换
```
Map: 键值对集合。
toJS把Map转换成原生的Object,深转换(无论有多少层级都会转换)
toJSON/toObject把Map转换成原生的Object,浅转换(只转换第一层)
```
```js
let {Map}=require("immutable")
let a=Map({
b:Map({
c:Map({
text:"你好么么哒"
})
})
})
//toJS() 深转换
let newdemob=a.toJS()
console.log(newdemob)
// toJSON()/toObject() 浅转换(只转换第一层)
let newdemo=a.toJSON()
console.log(newdemo)
```
### toArray ()转换成数组 浅转换
```js
let {Map}=require("immutable")
let demo=Map({
name:"xixi",
age:18
})
let newdemo=demo.toArray()
```
## immutable-List
```
有序可以重复的列表,对应于Array
```
### 创建List
```js
let {List}=require("immutable")
// 1.创建
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
// 第二种方式List.of()
let arr1=List.of(1,2,3,4)
```
### size获取list长度
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
console.log(arr.size)
```
### set(下标,值)用于设置指定下标的值
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.set(1,"我被修改了")
let newarr=arr.set(10,"我被修改了")//可以大于数组长度
let newarr=arr.set(-1,"我被修改了")//负数从右往左
```
### delect(下标)删除指定下标
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.delect(1,)
let newarr=arr.delect(-1,)//负数从右往左
```
### insert()用来更新指定下标的值
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.insert(1,"我被修改了")
let newarr=arr.insert(-1,"我被修改了")//负数从右往左
```
### update(下标,回掉函数)用于更新指定下标的值
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.update(1,x=>x+"更新了")
```
### clear()清空并且返回一个空list
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.clear()
```
### setSize()重新设置数组长度
```
setSize()重新设置数组长度,小于原始list会被截取 ,大于会用undefined填充
```
```js
let {List}=require("immutable")
let arr=List(["aaaa","bbb","ccc","ddd","eee","fff"]);
let newarr=arr.setSize(2)
let newarr=arr.setSize(10)
```
### 其他
```
push pop unshift shift 和数组方法项功能相同
concat() 把多个list拼接成一个list,类似数组拼接
merge()是concat()别名,和concat()作用一样
```
# immutable在react中使用
```jsx
import React, { Component } from 'react'
//引用子组件
import Zi from "./zi.jsx"
import {Map} from "immutable"
export default class fu extends Component {
constructor(props){
super(props)
this.state={
list:Map({})
}
}
add(){
console.log(this.inputa.value+"----"+this.inputb.value)
// this.state.list[this.inputa.value]=this.inputb.value
let newlist=this.state.list.set(this.inputa.value,this.inputb.value)
this.setState({
// list:this.state.list
list:newlist
},()=>{
console.log(this.state.list)
})
}
render() {
return (
)
}
}
```
```jsx
import React, { Component } from 'react'
export default class zi extends Component {
render() {
let {zilist}=this.props
return (
{/* 如何遍历对象 */}
{
Object.keys(zilist.toJS()).map((v)=>{
return (
商品名{v}----数量:{zilist.get(v)}
)
})
}
)
}
}
```
# typescript + react
## 什么是TypeScript
TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上TypeScript扩展了JavaScript的语法解决JavaScript的“痛点”:弱类型和没有命名空间,导致很难模块化。
TypeScript是js的超集,可以编译为纯js
Ts支持es6的标准,是微软开发的,是开源的,可以在任何操作系统上运行
## 为什么要用TypeScript
### 开源
### 简单
TypeScript 是 JavaScript 的超集,这意味着他支持所有的 JavaScript 语法。
### 兼容性好
TScript 是 JS的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JS,所以 TScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。任何现有的JS程序可以不加改变的在TScript下工作。
## 与js相比的优势
```
TypeScript工具使重构更变的容易、快捷。
TypeScript 引入了 JavaScript 中没有的“类”概念。
TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中
类型安全功能能在编码期间检测错误,这为开发人员创建了一个更高效的编码和调试过程。
```
## 安装
```
node //服务器
TypeScript解释器 npm install -g typescript //-g 全局安装 ,在本机上 哪里都能用
tsc 空格 -v //命令用来测试是否安装成功
```
## 文件声明与使用
```
文件后缀 :文件名称.ts
每次修改.ts文件都需要编译 使用命令: tsc 文件名
会自动生成xxx.js文件
如果想使用必须在HTML文件中使用script标签引用新生成的xxx.js文件
```
## 变量
```
创建变量的时候需要指定数据类型:let num:number=123;
```
```
let/var 变量名称:数据类型 = value
let(块变量)和const(常量)是JavaScript里相对较新的变量声明方式。
注意:let变量不能重复声明
注意:const它拥有与 let相同的作用域规则,但是不能对它们重新赋值。
注意:除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格
```
## 数据类型
### 普通的
```js
// 数据类型
let text:string="我是一个字符串";//单引号双引号都是可以的
let num:number=123;
let bool:boolean=false;
console.log(text+"---"+num+"----"+bool)
```
### 数组
```
数组 :number[]或 :Array
```
```js
// 创建数组
let arr:number[]=[11,222,333,444,555]
let arrb:Array=[99,88,77,66,55]
```
### 枚举 enum
```
枚举 enum 类型是对JavaScript标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字。枚举表示的是一个命名元素的集合值
没有加""
默认情况下,从0开始为元素初始值。 你也可以手动的指定成员的数值。
枚举:用于定义数值的集合
```
```js
enum user {xiaoming,xiaohong,xiaohuang}
enum user {xiaoming="小明",xiaohong="小红",xiaohuang="小黄"}
console.log(user.xiaoming+"---"+user.xiaohong)
```
### Any任意类型
```
Any任意类型 在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量
```
```js
let text:any=true
```
### 元组
```
元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同(赋值的顺序不能变)
表明一直元素数量和类型的数组各个元素的类型可以不同 但是对应的位置必须相同
```
```js
let demo:[string,number,boolean]=["字符串",1,true]
console.log(demo[0])
```
### never
```
never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。
```
### Void
```
Void没有任何类型
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
```
## 解构/析构
```
解构。结构是一种打破数据结构,将其拆分为更小的部分的过程
从对象和数组中获得特定的数据并且赋值给变量,编写出了很多同质化的代码。
```
### 解构/析构--对象
```js
let o = {
a:"aa",
b:"bb",
c:"cc"
}
let {a,b} = o
```
```
如果我们要把一个对象当中的值以此赋值给多个变量那么我们可以使用对象解构
a和b是声明的变量,也是从obj对象读取响应值的属性名称(变量名和属性名必须相同)
```
### 解构/析构--数组
#### 1-如果我们要把一个数组当中的值以此赋值给多个变量那么我们可以使用对象解构
```js
var aa:string[]=["aa","bb","ccc"]
function fun(){
console.log(aa[1]);
}
```
#### 2-解构作为参数传递
```js
var arr:string[]=["aa","bb","ccc"]
function fun([a,b,c]=arr){
console.log(a)
}
fun()
```
#### 3-忽略数组中某些元素(只需要把忽略的位置空缺就好)
```js
let arr:number[]=[1,2,3,4,5];
let [a,,c,d,e]=arr;
console.log(c);
```
## 展开
```
展开和解构正好相反,将一个数组展开为另一个数组,或将一个对象展开为另一个对象。
```
### 数组展开
```js
let arra:number[]=[1,2,3];
let arrb:number[]=[4,5,6];
let arr:number[]=[...arra,...arrb,100];
console.log(arr);
```
### 对象展开
```js
var obj={
name:"xixi",
age:19
}
var newobj={...obj,sex:"男"}
console.log(newobj.name)
```
## 函数
### 函数分类
```
函数的分类 JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。
```
```js
// 命名函数
function 函数名称(参数1,参数2):number{//返回值类型number
return 111;返回
}
// 匿名函数
var 函数名称 = function(参数1,参数2):number{
}
// 注意:参数类型必须与定义一致
// 返回值类型与函数类型一致
```
命名函数与匿名函数 可以给每个参数添加类型之后再为函数本身添加返回值类型。
TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。
```js
function test(x:number,y:number):number{
let a=x+y;
return a
}
var sum = text(3,3);
console.log(sum);
```
### 带有默认值的参数
```js
function test3(name="小伙",love="钱"){
var s:string = name+"喜欢"+love;
return s;
}
```
### 可变参数 当函数中的参数不确定个数的时候使用可变参数
```js
// 可变参数---->数组(集合)传参
function text(one:string,...arr:string[]){
var str = one+"-----"+arr.join("-");
return str;
}
var atr4 = test("aa","aaa","hhh","jl")
```
```js
// 函数
function fun ():void{
console.log("函数")
}
function funb():string{
return "我是字符串的返回值"
}
// 函数形参默认值
function func(num="我是默认值"):void{
console.log(num)
}
func()
```
## 接口
```
接口:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用
使用interface关键字定义 接口一般首字母大写 有的编程语言中会建议接口的名称加上 I 前缀
接口使用:使用:号接口名来进行使用
注意:定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的。赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性:可选属性的含义是该属性可以不存在 有时候不要完全匹配一个接口,那么可以用可选属性。使用?号
```
```
interface IProps{
text:string,
}
```
## ts与react
### 创建项目
```
创建新工程 create-react-app 3.x.x
创建项目:create-react-app my-app --typescript
```
```
创建新工程create-react-app 4.x.x 最新
如果不是最新的会报错 解决方式卸载掉原有的create-react-app
npm uninstall -g create-react-app
重新全局下载create-react-app
创建项目 create-react-app 项目名 --template typescript
```
```
创建项目:npx create-react-app 项目名 --template typescript
在 npm version >= 5.2.0 开始,自动安装了npx。
npx 这条命令会临时安装所依赖的环境。命令完成后原有临时下载的会删掉,不会出现在 全局中。下次再执行,还是会重新临时安装。不用全局安装,不用担心长期的污染。
也就是说 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装
```
### 文件结构
```
tsconfig.json包含了工程里TypeScript特定的选项。
tslint.json保存了代码检查器,TSLint将要使用的设置。
package.json包含了依赖,还有一些命令的快捷方式,如测试命令,预览命令和发布应用的命令。
public包含了静态资源如HTML页面或图片。除了index.html文件外,其它的文件都可以删除。
src包含了TypeScript和CSS源码。index.tsx是必须的入口文件
```
### 组件创建
```
创建一个tsx后缀名的文件
在需要的位置引用使用 但是引用的时候可能会出错
可能出现一下错误
在引用时候不要加后缀名
```
## 数据传递
### 父传子
```
在进行数据传递的时候要使用 interface接口对类型限制
在子组件中进行限制设定
```
```jsx
//子组件
import React, { Component } from 'react'
interface IProps{
text:string,
}
export default class zi extends Component {
render() {
return (
我是子组件---{this.props.text}
)
}
}
```
```jsx
//父组件
import React, { Component } from 'react'
import Zi from "./zi"
export default class home extends Component {
render() {
return (
我是ts的组件
)
}
}
```
```
在接口中定义的内容必须全部传递不能只定义不传值
```
```jsx
import React, { Component } from 'react'
// 定义的接口默认情况下都要进行使用
// 但是如果我定义的接口数据我不知道他是不是要是用怎么办?
// 就是在定义接口数据的时候使用? 来表示可选参数
interface IProps{
text:string,
num?:number
}
export default class zi extends Component {
render() {
return (
我是子组件---{this.props.text}
)
}
}
```
### 子传父
```jsx
//父组件
import React, { Component } from 'react'
import Zi from "./zi"
interface IState{
text:string
}
export default class fu extends Component<{},IState> {
public constructor(props:any){
super(props)
this.state={
text:""
}
}
public fun=(val:any)=>{
console.log(val)
this.setState({
text:val
})
}
render() {
return (
我是接收逆向传值的父组件--{this.state.text}
)
}
}
```
```jsx
//子组件
import React, { Component } from 'react'
interface IProps{
demofun:any
}
export default class zi extends Component {
public fun=()=>{
this.props.demofun("我是子组件的数据!!")
}
render() {
return (
我是给父组件进行逆向传值
点我进行逆向传值
)
}
}
```
## 状态
### 定义状态
```
直接定义状态会出现问题,因为没有设置数据类型
所以定义状态必须定义接口类型
```
```jsx
import React, { Component } from 'react'
import Zi from "./zi"
interface IState{
num:number
}
// 组件在接收接口的时候 第一位是props的 第二位才是state的,如果是空,可以加个空对象
export default class home extends Component<{},IState> {
public constructor(props:any){
super(props)
this.state={
num:123
}
}
public render() {
return (
我是一个ts的组件
{this.state.num}
)
}
}
```
### 修改
```jsx
import React, { Component } from 'react'
import Zi from "./zi"
interface IState{
num:number
}
// 组件在接收接口的时候 第一位是props的 第二位才是state的
export default class home extends Component<{},IState> {
public constructor(props:any){
super(props)
this.state={
num:123
}
}
public fun=()=>{
this.setState({
num:9527
})
}
public render() {
return (
我是一个ts的组件
{this.state.num}
点我修改
)
}
}
```
## 请求数据
```
http://api.artgoer.cn:8084/artgoer/api/v1/user/324380/v3/topic/topicHomeByLabel?pageIndex=1&token=b544cd63-6d42-46fe-a96c-3cf96bae3113&topicId=62187
```
```jsx
import React, { Component } from 'react'
interface IState{
data:any
}
export default class fetchdemo extends Component<{},IState> {
public constructor(props:any){
super(props)
this.state={
data:[]
}
}
componentDidMount() {
fetch("http://api.artgoer.cn:8084/artgoer/api/v1/user/324380/v3/topic/topicHomeByLabel?pageIndex=1&token=b544cd63-6d42-46fe-a96c-3cf96bae3113&topicId=62187")
.then(res=>res.json())
.then((ok)=>{
console.log(ok.data.commentList)
this.setState({
data:ok.data.commentList
})
})
}
render() {
return (
请求数据
{
//第一次有可能是空的,判断length,有数据再遍历
this.state.data.length!=0? this.state.data.map((v:any,i:any)=>{
return (
{v.commentTxt}
)
}):请稍等
}
)
}
}
```
# umiJS
```
Umi 是蚂蚁金服的底层前端框架 中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备。同时有强大的插件扩展各种功能
umi可以更好的组织路由 放到配置文件中统一管理
```
## 什么时候不用 umi
```
需要支持 IE 8 或更低版本的浏览器
需要支持 React 16.8.0 以下的 React
需要跑在 Node 10 以下的环境中
有很强的 webpack 自定义需求和主观意愿
需要选择不同的路由方案
```
## 不同
```
create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。如果想基于他修改部分配置,或者希望在打包层之外的事情,就会遇到困难。
```
## 使用
```
确保电脑上有nodejs 10.13 或以上
```
## yarn
### 下载
```
npm install -g yarn
```
### yarn与npm
```
初始化 : yarn init npm init
安装依赖: yarn install 或者 yarn npm install
新增依赖: yarn add xxx npm install xxx --save
删除依赖 : yarn remove xxxx npm uninstall xx --save
全局安装全局: yarn global add xxx npm install xx -g
同时下载多个: yarn add xxx xxx npm install --save xxx xxx
```
### yarn更换镜像源
```
yarn config set registry https://registry.npm.taobao.org
```
## umi下载安装
```
npm install -g umi / yarn global add umi 全局安装
查看版本 umi -v
安装项目 npm或yarn create @umijs/umi-app
安装依赖 cnpm install 或 yarn
启动项目 npm 或 yarn start
运行 http://localhost:8000
```
## HTML模板
```
修改默认模板
新建 src/pages/document.ejs,umi 约定如果这个文件存在,会作为默认模板
作用:如果要引用第三方js/css文件 可以直接在HTML模板中进行配置。有很多第三方的插件都没有react版本的 所以采用import的方式是没有办法进行引用的 也就只能采取上面的方式进行引用
```
```html
Document
```
## 页面创建
```
umi g命令创建页面或者组件
在项目根路径下执行
umi g page xxx(页面名)或umi g page xxx/xxx
umi g page xxx--typescript --less 创建ts页面与less
也可以自己创建react 类组件
```
## 路由
在 Umi 中,应用都是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。
## 配置路由
```
在配置文件.umirc.ts中通过 routes 进行配置,格式为路由信息的数组。
path:路径
component:组件路径 @代表src路径
exact:路径精准匹配
```
```jsx
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{ path: '/', component: '@/pages/index' },
{ path: '/home', component: '@/pages/home' },
{ path: '/phone', component: '@/pages/phone' },
],
fastRefresh: {},
});
```
### title:当前页面标题
## 页面跳转
在 umi 里,页面之间跳转有两种方式:声明式和命令式。
### 声明式
通过 Link 使用,通常作为 React 组件使用
```jsx
import React, { Component } from 'react'
import {Link} from "umi"
export default class home extends Component {
fun=()=>{
history.push("/phone")
}
render() {
return (
home
点我去phone
)
}
}
```
### 命令式 /编程式
通过 history 使用,通常在事件处理中被调用。
```jsx
import React, { Component } from 'react'
import {history} from "umi"
export default class home extends Component {
fun=()=>{
history.push("/phone")
//返回上一个路由
// history.goBack()
}
render() {
return (
home
点我使用编程式导航来进行
)
}
}
```
## 重定向使用redirect
```jsx
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{exact:true,path:"/",redirect:"/index"},
{ path: '/index', component: '@/pages/index',title:"首页" },
{ path: '/home', component: '@/pages/home' ,title:"home" },
{ path: '/phone', component: '@/pages/phone',title:"phone" },
],
fastRefresh: {},
});
```
## 二级路由
```
routes:配置路由的子路由
然后在 一级路由中 中通过 props.children 渲染子路由
```
```jsx
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
routes: [
{exact:true,path:"/",redirect:"/home"},
{ path: '/index', component: '@/pages/index',title:"首页" },
{ path: '/home', component: '@/pages/home' ,title:"home" },
{
path: '/phone',
component: '@/pages/phone',
title:"我是phone",
routes:[
{path:"/phone/era",component:"@/pages/er/era"},
{path:"/phone/erc",component:"@/pages/er/erc"},
]
},
fastRefresh: {},
});
```
```jsx
import React from 'react';
import styles from './phone.css';
import {Link} from "umi"
export default function Page(props) {
return (
Page phone
era
erc
{props.children}
);
}
```
## 约定式路由
除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录拿到路由配置。
但是实际上你写了文件之后,手动在浏览器地址栏输入路由没有发生跳转 原因: 使用脚手架搭建的项目会在配置文件中对路由进行配置。
解决删除.umirc.ts路由配置文件
## 约定式路由--动态路由
```
约定 [] 包裹的文件或文件夹为动态路由。
```
```jsx
//接收
import React, { Component } from 'react'
export default class xiaoming extends Component {
render() {
return (
我是约定是路由接收参数--{this.props.match.params.xiaoming}
)
}
}
```
```jsx
import React, { Component } from 'react'
import {Link} from "umi"
export default class home extends Component {
render() {
return (
home
{/*发送参数*/}
点我去user并且传递参数
)
}
}
```
# Hook
React Hooks是React 16.8.0版本推出的新特性 主要的作用就是让无状态组件 可以使用状态和react的其他特性。(在react开发中状态的管理是必不可少的 以前为了进行状态管理需要使用类组件或者redux等来管理)
Hook在class中没有作用
```
传统的无状态组件/函数组件 不能使用state状态 也不能使用生命周期等内容
hook 就是让无状态组件可以使用状态等react的其他特性 这个是react16.8新增一个特性
```
## useState()
```
useState()就是React提供最基础、最常用的Hook,主要用来定义和管理本地状态。
useState返回的是一个数组(长度为2),数组的第一个对象表示当前状态的值,
第二个对象表示用于更改状态的函数,类似于类组件的setState。
let [val(当前状态的值),setVal(改变状态的函数)]=useState(当前状态的初始值)
```
```
useState 在使用的时候必须创建一个无状态组件 也就是函数组件
函数组件的首字母大写首字母大写首字母大写首字母大写
创建: useState() 返回的是一个数组
使用的时候 let [变量名(获取数据的时候使用的),修改的方法名(修改数据的时候使用的)]=useState(初始化数据)
如果想创建多个状态数据 那么有两种写法 1.多次创建 2.传递一个对象进行创建
```
### useState使用
```jsx
import React, { Component ,useState} from 'react'
let Demoa=()=>{
let [num,setNum]=useState(9527)
return (
我使用来测试usestate的hook组件
使用useState的数据-----{num}
)
}
export default Demoa
```
### useState修改
```jsx
import React, { Component ,useState} from 'react'
let Demoa=()=>{
let [num,setNum]=useState(9527)
let del=()=>{
setNum(num-1)
}
return (
我使用来测试usestate的hook组件
使用useState的数据-----{num}
{/*修改*/}
{setNum(num+1)}}>点我+1
点我-1
)
}
export default Demoa
```
### useState多个状态
#### 1-对象类型
```jsx
let[num,setNum]=useState({
num1:1,
num2:2,
num3:3
})
```
#### 2-多次声明
```jsx
let [num,setNum]=useState(9527)
let [num1,setNum1]=useState(9528)
let [num2,setNum2]=useState(9529)
```
## useRef()
```
hooks中可以通过 useRef()获取Dom节点
```
```jsx
import React, { Component,useRef } from 'react'
let Demob=()=>{
//创建
let u=useRef(null)
let fun=()=>{
//使用
console.log(u.current.value)
}
return (
我是来测试useRef的组件
{/*绑定*/}
点我获取到输入框的值
)
}
export default Demob
```
## useEffect()
```
useEffect:函数组件中没有生命周期,那么可以使用 useEffect 来替代。
可以把 useEffect Hook 看做
componentDidMount,
componentDidUpdate,
componentWillUnmount,
这三个函数的组合。
```
```jsx
import React, { Component,useEffect } from 'react'
let Democ=()=>{
useEffect(()=>{
console.log("我是useEffect自动调用了")
document.title="我修改标题"
})
return (
我是用来测试useEffect的组件
)
}
export default Democ
```
## useReducer ()
```
useReducer这个跟redux一点关系都没有…它的主要作用是防止state里面的数据操作太复杂。减少state的数据操作复杂度
```
```jsx
import React, { Component,useReducer } from 'react'
let Demod=()=>{
let reducer=(state,action)=>{
switch (action.type) {
case "ADD":
return state+1
break;
case "DEL":
return state-1
break;
default:
return state
break;
}
}
// 创建 9527是初始化值
//state当前状态值
//dispatch 修改操作,dispatch一个对象
let [state,dispatch] = useReducer(reducer,9527)
let add=()=>{
dispatch({type:"ADD"})
}
let del=()=>{
dispatch({type:"DEL"})
}
return (
我是用来测试useReducer的组件
{state}
+1
-1
)
}
export default Demod
```
# dva
## 为什么学习dva
```
React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。
如果开发大应用,还需要解决一个问题。
通信:组件之间如何通信?
数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等
```
## 通信问题
```
组件会发生三种通信。
向子组件发消息
向父组件发消息
向其他组件发消息
React 只提供了一种通信手段:传参。对于大应用,很不方便
```
## dva 是什么
```
dva 是体验技术部开发的 React 应用框架,将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。
dva = React-Router + Redux + Redux-saga
dva名字来自于守望先锋的D.Va(是守望先锋中的一个英雄)
```
## 安装 dva-cli
```
全局安装dva-cli npm install dva-cli -g
查看版本:dva -v
创建新应用:dva new 项目名
启动命令:npm start
```
## 定义路由
```
路由页面创建在dva提供的router文件夹下创建
添加路由信息到路由表,编辑 router.js
+ import Products from './routes/Products';
...
+
```
### 1-在router下创建路由页面
```jsx
import React, { Component } from 'react'
export default class home extends Component {
render() {
return (
我是home页面
)
}
}
```
### 2-在router.js中引用配置
```jsx
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';
//引用页面
import Home from "./routes/home"
function RouterConfig({ history }) {
return (
{/*配置路由*/}
);
}
export default RouterConfig;
```
## 定义路由导航
```
在dva中使用路由导航要从dva/router中进行引用
```
### 声明式
```jsx
import React, { Component } from 'react'
//引用link
import {Link} from "dva/router"
export default class home extends Component {
render() {
return (
我是home页面
{/*配置路由导航*/}
点我去欢迎页面
)
}
}
```
### 编程式
```jsx
import React, { Component } from 'react'
//引用link
import { Link } from "dva/router"
export default class home extends Component {
fun = () => {
// 编程式导航
this.props.history.push("/")
}
render() {
return (
我是home页面
{/*配置路由导航*/}
点我去欢迎页面
{/*使用编程式导航*/}
点我使用编程式导航
)
}
}
```
## 定义路由模式
```
切换 history 为 browserHistory
需要下载history 依赖:npm install --save history
然后修改入口文件:
import { createBrowserHistory as createHistory } from 'history';
const app = dva({
history: createHistory(),
});
```
### 在src下的index.js文件中修改
```js
import dva from 'dva';
import './index.css';
// 1. Initialize
// 原本的默认模式
// const app = dva();
//切换 history 为 browserHistory
import { createBrowserHistory as createHistory } from 'history';
const app = dva({
history: createHistory(),
});
// 2. Plugins
// app.use({});
// 3. Model
// app.model(require('./models/example').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');
```
## 定义组件
```
组件在components下进行定义
在需要的地方引用使用
定义组件的方式可以定义无状态组件
无状态组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,
精简至只有一个render方法,大大的增强了编写一个组件的便利
```
### 无状态组件的特点
```
组件不会被实例化,整体渲染性能得到提升
因为组件被精简成一个render方法的函数来实现的,无状态组件就不会在有组件实例化的过程,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升。
组件不能访问this对象
无状态组件由于没有实例化过程,所以无法访问组件this中的对象,若想访问this就不能使用这种形式来创建组件
组件无法访问生命周期的方法
```
## Model
```
model 用来处理数据和逻辑。可以把数据和交互都放到model中方便使用
```
## 使用Mosel示例
### 1-创建两个组件
#### demoa组件
```jsx
import React, { Component } from 'react'
export default class demoa extends Component {
render() {
return (
我是一个类组件
)
}
}
```
#### demob组件
```jsx
import React from 'react'
let Demob=(props)=>{
return (
我是一个函数组件
)
}
export default Demob
```
### 2-引用上面两个组件
```jsx
import React, { Component } from 'react'
import Demoa from "../components/demoa.jsx"
import Demob from "../components/demob.jsx"
export default class home extends Component {
render() {
return (
我是home页面
)
}
}
```
### 3-在models文件夹中创建 对应的model文件
#### demoamodel.js文件
```js
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
//保存数据的
state: {
age:18
},
};
```
### 4-在全局文件 index.js中进行引用
```js
import dva from 'dva';
import './index.css';
// 1. Initialize
// 原本的默认模式
// const app = dva();
//切换 history 为 browserHistory
import { createBrowserHistory as createHistory } from 'history';
const app = dva({
history: createHistory(),
});
// 2. Plugins
// app.use({});
// 3. Model
// app.model(require('./models/example').default);
//引用demoamodel.js
app.model(require('./models/demoamodel').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');
```
### 5-在需要使用的组件中使用connect进行连接
#### demoa中使用
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
render() {
return (
我是一个类组件---{this.props.state.age}
)
}
}
export default connect(state=>({state:state.demoamodel}))(demoa)
```
#### demob中使用
```jsx
import React from 'react'
import {connect} from "dva"
let Demob=(props)=>{
return (
{/*3使用数据,无状态组件不需要this*/}
我是一个函数组件--{props.name}
)
}
//1设置数据
const mapState=(state)=>{
return {
name:state.demoamodel.age
}
}
//传递HOC
export default connect(mapState)(Demob)
```
# dva-reducers
## 修改-Model数据
### 1-在model文件中定义reducers 属性来进行修改动作
```js
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return state
}
}
};
```
### 2-在组件中使用dispatch调用reducer修改动作
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
fun=()=>{
//调用修改
this.props.dispatch({
//type:"命名空间/reducers的名字"
type:"demoamodel/upage"
})
}
render() {
return (
我是一个类组件---{this.props.state.age}
点我修改
)
}
}
export default connect(state=>({state:state.demoamodel}))(demoa)
```
### 3-传递修改数据
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
fun=()=>{
//调用修改
this.props.dispatch({
//type:"命名空间/reducers的名字"
type:"demoamodel/upage",
data:"我是传递的修改的数据"
})
}
render() {
return (
我是一个类组件---{this.props.state.age}
点我修改
)
}
}
export default connect(state=>({state:state.demoamodel}))(demoa)
```
### 4-reducers中接收数据并且修改
```js
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return {...state,age:payload.data}
}
}
};
```
# dva-effects
## es6 Generator
```
Generator 主要是异步编程,用来封装一个异步任务,是一个异步任务的容器。
特点:交出普通函数的执行权(可以让函数在调用的时候按照我们的需要执行或者是暂停)
普通函数:在调用的时候函数中的内容会全部执行。
generator函数 可以让函数体内的内容随着我们的需要走走停停
在声明函数的function关键字和函数名之间有一个*号(用于区别普通函数)
yield(是异步不同阶段的分割线)在generator函数体内进行使用,可以定义不同的内部状态
使用next()来执行generator函数
调用generator函数的时候会返回一个generator对象
使用next()方法来执行generator函数
可以在next()方法中方法中传递参数 可以吧值传递到函数中对应的yield中。第一次不会传递
```
```html
Document
```
# dva--异步操作
```
effect 在 dva 框架下就是用来处理异步操作的
```
## 1-在model文件中写入effects属性
```js
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return {...state,age:payload.data}
}
},
//异步操作
effects:{
*eff({payload},{put,call}){
yield console.log("我是effect异步操作")
}
}
};
```
## 2-在组件内进行调用 触发方式也是dispatch
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
fun=()=>{
//调用修改
this.props.dispatch({
//type:"命名空间/reducers的名字"
type:"demoamodel/upage",
data:"我是传递的修改的数据"
})
}
funb=()=>{
this.props.dispatch({
// type:"命名空间/effects的名字"
type:"demoamodel/eff"
})
}
render() {
return (
我是一个类组件---{this.props.state.age}
点我修改
点我异步操作
)
}
}
export default connect(state=>({state:state.demoamodel}))(demoa)
```
# dva-基本异步请求
## 1--在services文件夹下封装自己的请求
```
异步请求在services文件夹下
新建文件封装我们自己的请求
```
```js
//从example.js复制
import request from '../utils/request';
export function query() {
return request('/api/users');
}
```
## 2-传入接口
```
传入请求地址 中国天气网测试请求地址(需要解决跨域)http://www.weather.com.cn/data/cityinfo/101320101.html
```
```js
import request from '../utils/request';
export function query() {
return request('http://www.weather.com.cn/data/cityinfo/101320101.html');
}
```
## 3-在组件内调用封装的请求
```jsx
import React, { Component } from 'react'
//引用link
import { Link } from "dva/router"
import Demoa from "../components/demoa.jsx"
import Demob from "../components/demob.jsx"
//引用封装的请求
import * as homeapi from "../services/homeapi.js"
export default class home extends Component {
//在生命周期里面请求数据
componentDidMount() {
homeapi.query().then((ok)=>{
console.log(ok)
})
}
fun = () => {
// 编程式导航
this.props.history.push("/")
}
render() {
return (
我是home页面
{/*配置路由导航*/}
点我去欢迎页面
点我使用编程式导航
)
}
}
```
## 4-解决跨域
### 4.1-在.webpackrc文件中设置跨域 并且修改请求
```
{
"proxy":{
"/api":{
"target":"http://www.weather.com.cn",
"changeOrigin":true,
"pathRewrite":{
"^/api":"/"
}
}
}
}
```
### 4.2-修改请求
```js
import request from '../utils/request';
export function query() {
return request('api/data/cityinfo/101320101.html');
}
```
# dva-model异步请求
### 1-在model中发送请求
```
如果需要在model中进行异步请求的话需要在effects中的call进行一步操作的发送
```
```js
//引用封装的请求
import * as homeapi from "../services/homeapi.js"
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return {...state,age:payload.data}
}
},
//异步操作
effects:{
*eff({payload},{put,call}){
//请求数据
let api = yield call(homeapi.query)
console.log(api)
}
}
};
```
### 2-页面内调用带有异步请求的effects
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
fun=()=>{
//调用修改
this.props.dispatch({
//type:"命名空间/reducers的名字"
type:"demoamodel/upage",
data:"我是传递的修改的数据"
})
}
funb=()=>{
this.props.dispatch({
// type:"命名空间/effects的名字"
type:"demoamodel/eff"
})
}
render() {
return (
我是一个类组件---{this.props.state.age}
点我修改
点我请求数据
)
}
}
export default connect(state=>({state:state.demoamodel}))(demoa)
```
# dva-model异步请求修改state
## 1-可以通过put的方式修改state
```js
import * as homeapi from "../services/homeapi.js"
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return {...state,age:payload.data}
}
},
//异步操作
effects:{
*eff({payload},{put,call}){
let api = yield call(homeapi.query)
console.log(api.data.weatherinfo.city)
if(api.data){
yield put({
// 调用reducer修改动作
type:"upage",
data:api.data.weatherinfo.city
})
}
}
}
};
```
## 2-创建修改reducers
```js
import * as homeapi from "../services/homeapi.js"
export default {
namespace: 'demoamodel',//命名空间 就是给当前的模块起个名字 必须写
//创建state
state: {
age:18
},
//修改数据的
reducers:{
upage(state,payload){
console.log("我是修改动作")
//必须返回
return {...state,age:payload.data}
}
},
//异步操作
effects:{
*eff({payload},{put,call}){
let api = yield call(homeapi.query)
console.log(api.data.weatherinfo.city)
if(api.data){
yield put({
// 调用reducer修改动作
type:"upage",
data:api.data.weatherinfo.city
})
}
}
}
};
```
## 3-页面引用state数据给props并且展示
```jsx
import React, { Component } from 'react'
import {connect} from "dva"
class demoa extends Component {
fun=()=>{
//调用修改
this.props.dispatch({
//type:"命名空间/reducers的名字"
type:"demoamodel/upage",
data:"我是传递的修改的数据"
})
}
funb=()=>{
this.props.dispatch({
// type:"命名空间/effects的名字"
type:"demoamodel/eff"
})
}
render() {
return (
{/*展示数据*/}
我是一个类组件---{this.props.state.age}
点我修改
点我异步操作
)
}
}
//引用数据
export default connect(state=>({state:state.demoamodel}))(demoa)
```
# dva-subscription订阅
model中的subscription相当于一个监听器,可以监听路由变化,鼠标,键盘变化,服务器连接变化,状态变化等,这样在其中就可以根据不同的变化做出相应的处理,在这个subsription中的方法名是随意定的,每次变化都会一次去调用里面的所有方法,所以一边会加相应的判断。