React+umi+antd项目开发流程知识点

814 阅读28分钟

umi-react

01:准备工作

1,一个空的文件夹,然后使用命令 yarn create @umijs/umi-app创造一个umi开发的项目
2,编译器中先yarn一下 安装一下他需要的所有依赖包
3,项目开启命令 yarn start

02:项目目录分析

image.png

--package.json 这个里面装的是项目的依赖信息 插件和插件集

--umirc.ts 这个是核心配置也是项目启动前umi要检查的一些配置(我们亲切的叫它启动时配置) 例如 路由 面板启用 dva启用 第三方库。

--env 环境参数文件

--mock 约定文件 里面存储的是模拟的本地数据

--src

-.umi  项目编译后产生的文件放在这里。

-pages  各种业务组成的文件夹 你可以理解成这个东西里面就是放那些路由组件的。

03:umi开发理解的必备知识

03-1:Umi是什么?

  • 可以扩展的一个企业级应用开发框架
  • 内置路由 以路由为基础 支持约定式路由和配置路由--保证路由功能完备 只需要维护好路由表umi帮你渲染和检测路径变化以及展示对应的组件
  • 使用大量趋于完备的插件体系来极大化的让创造神来大部分精力只关注业务功能的开发,少量关注在UI界面的呈现

03-2:umi的配置

配置分为两个 :

第一个是项目启动时umi就会先扫描一下准备好前提工作 启动时配置文件 umirc.js在这个js中你需要配置插件和路由,骨架视图等等 。当路由特别多的时候我们为了可以简洁一下改配置文件把路由数据放在外面引进来做个配置启用就好了

怎么用???

在项目中我们往往会先删除掉这个文件采用跟它同级别下创建一个config/config.js文件 把umirc中的配置都拿过来 这样做的目的就是我可以把配置项的value值放在外面通过模块引入来使用简化了配置文件代码的混乱

image.png

第二种:运行时配置 主要是当项目运行到浏览器端的时候umi开始检查

约定src/app.js 文件作为运行时配置文件  在这里你可以写函数 和jsx以及浏览器的依赖不能引入node的依赖
  • 我们一般都是在这里做一些对路由的拦截器配置  
    
  • 对主面板的一些自定义渲染
    
  • 获取到全局initialState的方法搭配来实现登录的权限控制
    

03-3:umi的基础知识

03-3-1 路由

image.png

1.路由route:一个路由就是一组映射关系 依据着想去的路径 同时匹配一一对应着的组件{根据路径 ->去展示组件} 路由的表示关系中是不是需要配置什么路径 对应什么组件 以及要不要图标 是否开启权限 需不需要隐藏 等等... 那你看一组一组的key:value 来共同表示该路由那你说这个路由在数据中是不是应该用对象的表示形式来包裹。

image.png

2.路由器routes:当多个路由的时候我们需要一个完整的路由器来管理,,routers [{},{}]能装下多个对象(路由)的只有数组啊。。。

3.路由中的一些常用的配置项解释

routes: [
    {
    path: '/',//路径的通配符
    component: '@/pages/index',//当浏览器端的地址url的location值和我们定义的path匹配后用于渲染出对应的React组件。后面是组件的路径 @/默认指的是src下
    exact:true,//是否开启严格模式默认开启必须对应少一个都不行
    routes:[
      {}//给当前路由配置一下孩子路由,千万要注意一件事 子孩子的路径必须是完整的 因为,肯定先有父亲先把父亲的渲染出来才可以去渲染儿子。
    ],
    redirect:"/home",//如果满"/”这个路径那么你重新跳到我这个配置项定义的"/home"中
    },
  ],
  

4.路由进行跳转

    //利用的是umi中内置的history 
    import {history} from "umi"
    /*你想想我们都拿到历史记录对象了 什么地方我去不了啊  
       @关键是思考一下去哪? 用什么方法带我去?  需要带点参数怎么带?
       @遇到问题解决问题   去哪,,明显是你自己定义好要去的路径  
                          用什么方法呢?  .puch("去哪")
                          带参数 :
                              一种使用es6中的模板字符串路径和参数放在一起.
                              一种是给puch函数传递一个对象过去,对象包括着路径和参数
       */
      history.push(`/home?id=${myId}`) //跳转到home页面并且采用模板字符串query传参 路径和参数用?隔开 参数和参数之间用&隔开 参数是一组一组的key=value 关系 key是你指定的
      history.push({
          pathname:"/home",
          query:{
          id:myId
          }
      })//采用的是对象的跳转
      history.goBack()//从哪来的点击回退到哪 前提是历史记录站采用的不是replace替换

5.路由组件和普通组件有什么区别呢?

  第一:从名字上肯定不一样对不对  路由组件的使用依靠着路径的变化对应
  第二:最重要的就是 路由组件在渲染的时候就可以接收一些参数(是umi提供给它的),对于普通组件来说你接收什么参数完全取决于你的父亲给你传递啥了。他没传你那里面自然啥也没有
  • match,当前路由和 url match 后的对象,包含 paramspathurl 和 isExact 属性
  • location,表示应用当前处于哪个位置,包含 pathnamesearchquery 等属性
  • history,同 api#history 接口
  • route,当前路由配置,包含 pathexactcomponentroutes 等
  • routes,全部路由信息

03-3-2 mock数据的创建和使用规则

1.首先项目里面有一个mock文件夹,在这里面的所有js文件都是用来在后端没有完善的情况下我们自己测试用的
    第一步:已知该文件暴露出去一个对象 
    第二步:对象中的一组一组key:value 值 key"指定你采用什么方式 请求的api地址" : 数据
        表示 当request中的路径和请求方式都对上的话 就把这一组的value值数据包交给你
    第三步:使用mockjs.mock({XXX})你想返回的数据包值做一个快速的赋值
    GET 方式是查询
    DELETE:方式是删除 同时很可能他需要指定你的参数
    PUT:方式是更新  也需要您调用的时候能给参数更新谁

image.png

03-3-3请求 request useRequest

1. request 请求知识点
    首先你要明白 只要我们的数据是想请求的得来   都离不开想两个东西   (请求路径和请求方式)
    这个假设上面的图片中已经完善好模拟的后台:  1.请求路径是:"/lovestart"
                                            2.请求方式是:"GET"
                                            3.一旦匹配成功后他会返给我们一个想要的值:originLoveList 
      有了后端  那么前端怎么发送请求啊, 使用request()  类似于axios一样一样的
    request("路径",options) 第一个参数是请求地址去服务器上哪一个域名下的地址去请求 第二个参数是请求详情,{
    method:"请求方式",
    params:{
    a:1
    }
    }
    发送请求方式和参数

   你有两个地方可以使用这个  :第一种你直接在需要发送请求的地方直接使用request().then().then()
   
   第二种:你还有一个操作 那就是把这个请求操作封装在一个A函数中 然后我们在某个组件中想需要请求的地方去调用这个A函数  
   
   那么这个A函数就会帮我们去调用她内部写的request函数并且把响应数据也直接交到封装的该A函数上    好吧,我已经晕了,写个例子把!!!
   
   把请求函数request 封装在函数中 暴露出去的模块我们就叫他们是api
   

image.png 上面是我定义了一个getLoveData 数据的方法,供外界使用 --api 就是一群包装请求的方便函数罢了

2.useRequest()
在组件中 我们可以通过调用getLoveData().then() 这样来得到数据 但是你需要手动设置loading状态 请求前和请求后的值

也可以用let {data,error,loading}=useRequest(return 调用的请求函数) 
    首先我们先明白 使用useRequest这个有什么好处  其实就是对这个请求函数外面又多给了一层函数  你在这里面请求数据  但是我要求你返回的数据中必须有data这个属性值 不然的话我解构赋值也没有啊!肯定报错  除非你不用data 

let{data,error,loading,run} = useRequest(
    async(参数)=>{
        let res =  await getLoveData()
        return {data:res.想要的数据包名}
    },{}
)
第一个问题:为什么要加async await
第二个问题:为什么返回值要这样
第三个问题:为什么useRequest(()=>{},{manual:true})为啥这样设置

第一个: 如果你需要得到这个请求的数据也就是说你需要数据data 我们知道调用的这个函数是一个promise类型
的那么本家数据是这个类型则我们可以直接在他的上一层写上async 和本家数据执行写上await  这样我们就把
异步变成同步,目的是直接拿到这个数据包
第二个:因为你使用这个去包装一下请求函数api 他需要你的返回对象中有data 这个属性  所以你务必自己动手
给他返回对应的属性 不然,报错
第三个:如果你不写第二个参数manual:true 的时候代表组件一加载就会自动请求   但有时候我们可能需要手
动去触发,例如点击按钮添加或者查询了   此时此刻你只需要写一个manual 这样的话这个函数就会返回一个函数
run()  这个run() 其实你可以理解成是useRequest中的那个箭头函数参数的副本    首先是manual设置就会不让
他自动执行   然后你通过run(参数)这个函数啥时候调用那么useRequest 什么时候执行   同时run(参数)的
参数也会被箭头函数中的参数相同   这不就是他的一个副本  拿出来让你手动去决定什么时候运行吗?

eg:请求数据

image.png

请求数据的api

image.png

使用api请求的组件使用
   

import React from 'react'
import {Button} from "antd"
import {useRequest} from "umi"
// 引入请求api数据接口
import {getLoveData}from "../../api/loveStart"
export default function Home() {
    /*
        分析,
            第一我们准备好数据没?
            第二我们准备好去请求数据的api接口没
            第三:发送这个请求我们需不需要用到data  来决定要不要对返回的数据进行类型修改 
            你都不需要data 那我肯定不用修改直接return 请求函数() 就行 ,我们是因为你需要data  所以才异步变成同步 拿到数据
            修改返回数据的类型
            第四:我们是想自动执行  还是手动执行    来决定要不要第二个参数manual:true
    */ 
    let {data,loading,error}=useRequest(async()=>{
        // loading  默认是false 一旦这个成功则立刻变成true 所以他不需要我们去维护  需要data
        let res =  await getLoveData()
        // 先检查一下后端给我们发的数据包长什么样子是不是我们期望的,里面带有data数据是我们想要的。。
        console.log("res的数据包格式",res);
        // 这个我们在mock中自己定义好的带有data所以自然数据包自然是  可以直接返回
        return res
        
    })
    return (
        <div>
            <div>一上来就加载数据</div>
            <Button>点击获取数据</Button>
        </div>
    )
}

打印台:因为有data 所以我们可以直接返回    不用await和async的两种情况 2:后端返回的数据包中就是以data命名的我们不需要修改。。假如这个是dataList不是我们想要的data   那么我们返回的修改值是
return {data:res.dataList} 因为外面的对象解构赋值来决定了返回的是不是对象以及里面有哪些属性

image.png

useRequest 的手动请求
import React from 'react'
// 引入请求数据的api
import {getLoveData} from "../../api/loveStart"
// 引入useRequest()
import{useRequest} from "umi"
// 直接引入antd库
import {Button} from "antd"
export default function About() {
    /*
        分析:P需要用data 但是返回数据包中就有data 不需要优化
        需要手动触发  开启第二个参数{}设置manual:true 拿到在什么时机执行的副本函数run
        好比是箭头函数的副本函数   自然你给他参数箭头函数也一定能收到
    */ 
    let {data,loading,error,run} = useRequest(async(params)=>{
        // loading  默认是false 一旦这个成功则立刻变成true 所以他不需要我们去维护  需要data
        let res =  await getLoveData()
        // 先检查一下后端给我们发的数据包长什么样子是不是我们期望的,里面带有data数据是我们想要的。。
        console.log("res 我是手动触发的",res);
        console.log("params 这个是run函数我们共用的参数",params);
        // 这个我们在mock中自己定义好的所以自然数据包自然是  可以直接返回
        return res
    },{
        manual:true
    }) 
    return (
        <div>
            <div>手动请求</div>
            <Button type='primary' onClick={
                ()=>{
                    run("哈哈")
                }
            } >点我获取数据</Button>
        </div>
    )
}

控制台

image.png

多个useRequest 在一个组件中多次发送请求 例如刚开始请求查询api 还有删除api,更新api等等
来看一段例子不知道能不能瞬间猜出来 假设XXX是其他请求api函数

image.png

为啥会报错???仔细看 你使用了useRequest() 他的解构赋值 data,loading error   等等 这些是啥,能不能理解成变量,在
同一个函数作用域下能不能出现相同的两个变量名。答案是:绝对不能
那明白了  其他的都一样  是不是我么只要更改一下前面的这些变量名让他们保持不一样是不是就可以了。  是的,下面看一下两种写法

image.png

既然你不想变量名一样 那么我直接采用es6中的方法解构赋值同时更改一下变量名  data:DeleteData  这样我就可以拿到数据,、
名字自己起但最好见名之义  对于这个api 数据使用变成  DeleteData DeleteLoading DeleteError DeleteRun   怎么觉得好
像是这些东西要是能在外面包一层对象名就好了   名字不一样  自然里面一样自然是没有关系的啊

image.png

根据上面的思维反正你返回的是一个对象那么我先用一个变量名接收一下  这样我的Delete就作为标准  Delete.data    
Delete.loading  Delete.run()  定义的变量会存储你原本的数据和方法。用的时候对象.名字就行  这样就可以不用更改名字了。

04:umi项目的开发

04-01: 布局layout的启用和修改

首先是我们现在启动时配置上config.js中开启使用layout布局 在config.js中配置一下

image.png

布局的layout只关心路由你是怎么创建的,会把对应的路由导航放上去通过name值 如果你后期想更改原有的默认UI那么需要在 
app.js中我们通过暴露一个export const layout =(()=>{
return{
这里面的键值对可以更改原有的配置去prolayout高阶组件中去尝试
    rightRender:()=>{返回一个react元素} //自定义右边的UI
    footerRender:()=>{返回一个元素}// 自定义底部的UI
}
}
)

04-02:服务器和api 拦截器的流程

    首先我们以本地作为服务器后台
    我们定义的请求函数接口作为api
    组件调用请求的方法 api
    拦截器

首先我们设置本地数据模拟 data 然后定义请求数据方法api 以及组件中去使用这个方法调用 准备工作看下面 ————————————数据 image.png ——————————api方法

image.png ——————————组件中调用

image.png ——————————拦截器配置在运行时配置上 他返回的是一个request 对象 你自己需要配置一旦需要请求或者得到什么你需要做什么

/*
    这个是做项目运行时候的配置的,,layout 自定义  或者路由的拦截
    当你发送请求的时候我先拦住你,做些操作后由我给你发后端
    url 是你要往那里,也就是什么域名下的某一个具体的
    options  就是你要给这个路由拦截器配置的东西 完整地址和请求头
    拦截器是一个数组  里面是子元素是函数接收的参数也不一样  的作用是  
        当你开始向服务器发送请求的时候(有可能服务器要的太多但是我此时只想通过简单的路径去请求),那么安排拦截器,我先拦截下来帮你化个妆,补个水,,缝缝补补开心的去找服务器
        响应拦截器是服务器给我的东西  可能是一大包 但是我只关心我需要的数据 所以请求拦截器先拦截下来我帮你筛选筛选之后返回给你
        请求拦截器:[(url,options)=>{}]  options 更改它的请求头 和请求的完整地址
*/ 
// 路由拦截的配置
export const request ={
    // 请求拦截器 内部子元素是一个函数  想请求服务器啊那我给你化化妆 一旦api中request 发送请求那么我直接拿到此时此刻你的url  拼接成完整的去人家的地盘上去获取数据
    requestInterceptors:[
        (url,options)=>{
            // 保留原来的options 顺便更改一下他的url和headers
            console.log("请求拦截器",options);
            return options
        }//内部是一个函数
    ],
    // 响应拦截器:内部也是一个函数 返回值是教给api函数的
    responseInterceptors:[
        (response,options)=>{
            console.log("响应拦截器",response);
            return response
        }
    ]
}

准备工作做好了:开始执行。。

 首先:我们从组件中去请求-->引发了api(调用函数)
          |
      调用api函数(请求) 触发request函数执行
          |
      请求拦截器拦截下来-->化个妆封装一下交付给服务器
          |
      服务器检查你给我的是不是我想要的格式正确后
          |
      把你想要的数据包交付给响应拦截器---> 然后把数据返回给request()当他他的函数返回结果
          |
      然后组件中的调用的请求函数就可以拿到request请求之后的结果
      

image.png

04-03:用户的登录

04-03-01:准备工作展示UI界面

准备工作:先搭建  用form表单做主要内容呈现 外面被card组件裹住提升样式 最后利用Grid栅栏布局来去调整位置:首先注意一点
使用路由的时候我们可以设置一个配置layout:false  表示一旦你进入这个页面就不需要layout布局撑满全屏 
    {
        path:"/login",
        name:"登录",
        component:"@/pages/Login",
        layout:false
    }
]
开始搭建
import { Button, Checkbox, Form, Input ,Card,Col,Row} from 'antd';
import React from 'react';

const Login = () => {
    // 当你点击提交的时候会触发这个函数  values是表单帮你收集的他的子孩子中所有的value值
    const onFinish = (values) => {
        console.log('Success:', values);
    };
    return (
        <Row style={{
            height:"100vh",
            backgroundColor:"#ececec",
            backgroundImage:"url('https://ts1.cn.mm.bing.net/th/id/R-C.4d4f550f46961bb3f6cee424a5016f32?rik=o5AwvQVXuYkAlA&riu=http%3a%2f%2fimage.qianye88.com%2fpic%2f863fb09263bcb0f2f97bf7d77e7289a0&ehk=WRbw5YvsSK%2bsIeK%2bcU8ISSFIXl23buJ9nCRcN2hKW8Y%3d&risl=&pid=ImgRaw&r=0')",
            // 要不要平铺重复的进行先水平后垂直no-repeat不准平铺 图片多大占据多大 只渲染一次
            backgroundRepeat:"repeat",
            // 图片的起点位置 处于中间   自带Xy两种 也可以自定义指定位置
            backgroundPosition:"center",
            backgroundAttachment:"fixed"

        }}
        align="middle"
        >
            <Col 
                offset={8}
                span={8}
            >
                <Card 
                    title="用户登录"
                    extra={<Button type='primary'>更多</Button>}
                >
                    <Form
                        name="basic"
                        labelCol={{
                            span: 6,
                        }}
                        wrapperCol={{
                            span: 12,
                        }}
                        initialValues={{
                            remember: true,
                        }}
                        onFinish={onFinish}
                        autoComplete="off"
                        >
                        <Form.Item
                            label="用户名"
                            name="username"
                            rules={[
                            {
                                required: true,
                                message: 'Please input your username!',
                            },
                            ]}
                        >
                            <Input />
                        </Form.Item>

                        <Form.Item
                            label="密码"
                            name="password"
                            rules={[
                            {
                                required: true,
                                message: 'Please input your password!',
                            },
                            ]}
                        >
                            <Input.Password />
                        </Form.Item>

                        <Form.Item
                            name="remember"
                            valuePropName="checked"
                            wrapperCol={{
                            offset: 8,
                            span: 16,
                            }}
                        >
                            <Checkbox>是否记住密码</Checkbox>
                        </Form.Item>

                        <Form.Item
                            wrapperCol={{
                            offset: 8,
                            span: 16,
                            }}
                        >
                            <Button type="primary" htmlType="submit">
                            提交
                            </Button>
                        </Form.Item>
                    </Form>
                </Card>
            </Col>
        </Row>
    );
};

export default Login;

展示结果:

image.png

04-03-02:登录的逻辑操作,和插件的搭配

首先我们来认识两个插件  初始化状态的管理约定一个定义和使用初始化数据 
    app.js 中 定义一个生产初始化数据的方法名叫做 getInitialState(){}
              他的返回值会被umi包装一下装载你返回的对象中,直接透传(交付给)layout  
              Model + layout
              三者搭配形成登录的逻辑
              
 定义启用方式:在app.js中我们定义函数
 
// 用来处理登录

// 定义一个产生初始化数据的方法,保存登录用户的信息被其他插件使用去验证
export async function getInitialState() {
    // 刚开始我们自己定义一个初始化数据
    let userData = {
        login:false,//用来控制此时的用户登录状态是不是成功的
        userInfo:null,
    }
    return userData;
}

// 定义layout用来控制主面板和其他

export const layout = (initialStateObj)=>{
    /*
        但是在这里接收的却是把你传的那个变量名给你更改叫initialState等价userData 毕竟人家本来就
        是想定义初始化数据的,然后顺手还给你封装成一个对象 
    */
    console.log("我是从getInitialState函数返回的值教给layout",initialStateObj);
    let {initialState:{login}} = initialStateObj
    return{
        // 这个是控制主面板的配置选择
        onPageChange:()=>{
            // 这个配置项指的是只要页面发生切换 必须在这里拦截住 检查一下条件能不能被切换
            if(login){
                // 如果login是为真的话, 那就说明用户登录成功了,跳转主页面。
                history.push("/home")
            }else{
                // 用户还没登录那你就去登录页登录
                history.push("/login")
            }
        }
    }
}

image.png

既然你已经知道了我是通过判断我自己定义的初始化数据中login来看用户的登录状态的 ,肯定有个方法 在我们真的
点击登录按钮之后后台验证成功后给我们返回信息了,那么此时我按理说已经登录成功了,那么我需要修改这个全局共享
数据,,,你想修改数据  那么你首先要能拿到数据  umi提供了一种方法hook useModel
    getinitialState 函数 返回的数据会被umi封装好一个对象然后默认注入一个名字叫@@initialState 的模块中 
    我们做的就是从这个模块中取出getInitialState()函数的返回值
    let{initialState,loading,refresh,setInitialState} =useModel("@@initialState")
    
    initialState:就是你这个那个函数返回的数据  注意看我采用对象的解构赋值才拿到真的初始化数据
    
    loading : 默认是false 当你取出来的时候或者修改成功之后会变成true 表示是不是在加载中
    
    refresh: 刷新函数 你只要调用这个函数 它就会重新调用getInitialState函数 然后就会在主面板上重新判断
    
    setInitialState :这个指的是手动更改初始化的值 你登录成功后修改loading为真即可
  

04-03-03:登录流程

    用户一打开应用--->立刻app.js配置中的getInitialState()函数执行(第一次获取)--->(把数据教给layout) 
    layout通过拿到全局数据中的login 来判断此时应用该往哪里跳---> 如果没有登录-->跳到登录页点击登录触发
    useMOdel中的setInitialState函数修改状态  ---> 然后自动重新刷新getInitialState 函数开始第二次继续检查。

不成功

graph TD
用户刚打开应用 --> 执行getInitialState-->layout拿到值判断-->若login为假-->前往登录页执行setInitialState-->重新触发getInitialState函数

成功

graph TD
用户刚打开应用 --> 执行getInitialState-->layout拿到值判断-->若login为真-->跳转主页面

04-04:LeanCloud的使用

这个你只需要会建立表,按照它的规则去写就没问题

第一:你首先建立一个应用  一个应用就是你的数据仓库

image.png

  第二:进去这个应用,找到数据存储下的结构化数据这里就是存你的表的

image.pngdi

第三: 找到设置下的应用凭证  关注你的这个应用的iD Key  服务器地址(域名)

image.png

第四步:建表分为自己定义的表名  如果是的话 那么你请求的域名就是 服务器地址+1.1+classes+表名


第五步:更多请看文档

image.png

image.png

image.png

04-04-01:流程

首先:在路由拦截器中给options配上请求头和完整的路径 注意对自己的创立表务必请求的时候路径加上/classes/表名

image.png

其次在User中这个表写上你的username和password 目的是为了你登录的时候拿到这个数据跟后台做对比用的 
用户名:刘好杰,密码:666

image.png

然后创建api去给User表中发送验证  目的是得到后台给我的token信息 api/login.js
    import {request} from "umi"
// 创建用户登录接口   拿着用户输入的数据 然后教给login  这个表可不是自定义的看清楚
export const userLogin=(userObj)=>{
    return request("/login",{
        method:"POST",
        data:userObj
    })
}
登录组件中使用

image.png

产生的结果

image.png

组件刚上去我就要去检查这个data中有没有token 代表成功登陆   使用useEffect()搞定一波

image.png

这里面有个坑一定要注意表单的数据和我们自定义的数据是不是要产生关联更改值

下面开始更改我们初始化数据让他从存储内部去读取看看有没有 有的话就保存下来更改一下初始化数据,目的是刷新之后还会存在页面

export async function getInitialState() {
    // 刚开始我们自己定义一个初始化数据
    let userData = {
        login:false,//用来控制此时的用户登录状态是不是成功的
        userInfo:null,
    }
    // 先看存储内部有没有我们想要的数据
    let userInfo = localStorage.getItem("userInfo") || sessionStorage.getItem("userInfo") 
    if(userInfo){
        // 如果在缓存中是有的就代表是登录上的所以更改状态和用户信息
        userData={
        login:true,
        userInfo:userInfo
        }
    }
    return userData;
}

退出登录的逻辑 只有三步:

第一步:获取到全局共享数据 设为初始值

第二步:清空所有的缓存

第三步:跳转到登录页

image.png

04-05:Dva的使用和知识点

除了通过方法来控制一下这个初始值定义全局状态 那么有没有其他的方法呢?、

答案是有的。那就是 Dva 一点都不难,跟着我走吧

我们先介绍一下用到的插件 plug-dva 约定式的model组织方式

建立在src/models 默认他的子文件一个js文件就是一个业务数据仓库内部包含着该业务的数据和操作数据的方法,他的文件名就可以作为他的模块名

  • src/models 下的文件
  • src/pages 下,子目录中 models 目录下的文件
  • src/pages 下,所有 model.ts 文件(不区分任何字母大小写)

image.png

/*models/notice.js
    知识点:
        1:先去启动时配置中去开启dva     config.js中直接写上 dva:{immer:true} 开启dva和immer操作数据
        2:定义models文件 里面一个一个模块分别代表着一个一个仓库。文件名就是仓库名
        3:每一个仓库文件都默认暴露出一个对象 对象里面有三大核心对象分别是 state  Reducers  effect 分别是 存数据的,存操作本仓库数据的方法
            和操作本仓库数据的异步方法 乍一看好像(Vuex)的三部分  
        4:引入immer第三方库  因为react是浅层对比只知道看引用地址变不变 不变的话我不给你重新渲染 所以利用第三方库先把数据变成一个副本
            然后操作副本直接进行更改就可以 就能得到一个修改完的全新对象 produce(源对象,(副本)=>{对副本操作不需要返回值  会自动返回一个全新的
            })
        5:定义好数据 其他组件怎么用??
            记住谁用dva的状态 组件 要先定义 然后改造 利用connect((state)=>{
                return {
                    传递给子组件的名字:state.包名 哪一个模块下的state 
                }
            })(组件名)  就是做了一下封装  好比是给要用的组件外层包裹了一层父亲 ,你想跟dva状态机对话你通过父亲来说
            你想要数据,父亲组件沟通好 通过props 交给你这个数据  我的函数返回值就是交给我的孩子的,。你想操作reducers 肯定
            需要dispatch 函数 指定actions 类型 和操作的数据 恰好我给你传送数据的时候把这个函数也交给你 了。
        6:方法reducers   怎么用? 首先我们都知道Redux 中store 是通过dispatch(actions) 来通知reducers中对应的方法名  同样被connect封装不光把数据给你了同时还给你了
            dispatch在这个组件上  你直接传递指定类型和要操作的值
        7:从immer中借助produce 来产生一个副本去操作得到一个全新的数组
*/ 
import {produce} from"immer"
export default {
    // 装本仓库需要的全局数据的对象
    state:{
        messageList:[
            {
                picture:"https://randomuser.me/api/portraits/women/90.jpg",
                title:"消息1",
                detail:"我是消息1",
                isRember:true
            },
            {
                picture:"https://randomuser.me/api/portraits/women/90.jpg",
                title:"消息2",
                detail:"我是消息2",
                isRember:false
            },
            {
                picture:"https://randomuser.me/api/portraits/women/90.jpg",
                title:"消息3",
                detail:"我是消息3",
                isRember:true
            },
            {
                picture:"https://randomuser.me/api/portraits/women/90.jpg",
                title:"消息4",
                detail:"我是消息4",
                isRember:false
            },
        ]
    },
    reducers:{
        // 格式::::: 我们想让外部用我们的名字  接收一个actions 从dispatch传递过来的我们解析一下
        // 接收参数 想操作本家数据肯定能拿到本家的啊
        changeRember:(state,actions)=>{
            let {playload} = actions;
            // 给state做一个副本,然后这个副本得到的对象跟源对象一模一样 不同的是我可以直接下标操作
            let newData = produce(state,(draft)=>{
                draft.messageList[playload].isRember = true
            })
            // 把=更改好的数据教给全局数据
            return newData
        }
    }
}
----------------->Message 组件 用到的dva数据组件
import { Avatar, Button, List, Skeleton } from 'antd';
import React, { useEffect, useState } from 'react';
import {connect} from "umi"
const Message = (props) => {
    console.log("父亲传递给我的",props);
    let {messageList:{messageList}} =props
    return (
        <List
        className="demo-loadmore-list"
        itemLayout="horizontal"
        dataSource={messageList}//下面这个就是循环每一个
        renderItem={(item,index) => (
            <List.Item
            actions={[
                // 在jsx元素上 数组的子元素会是上一层的孩子元素结点
                <Button type="primary" disabled={item.isRember}
                    onClick={()=>{
                        // 点击未读去操作数据 我们只关注数据  数据在哪操作就在那我们只需要告诉他
                        props.dispatch({
                            // 指明你要去哪个模块下找reducers中的哪一个函数名 请你去notice模块下找到你需要的这个函数
                            type:"notice/changeRember",
                            playload:index//告诉他我要更改这条消息的isRember
                        })
                    }}
                > {item.isRember ? "已读":"未读"}</Button>
            ]}
            >
                {/* loading 指的是这个骨架组件要不要展示加载  如果你想展示数据就不要了 */}
            <Skeleton avatar title={false} loading={false} active>
                <List.Item.Meta
                avatar={<Avatar src={item.picture} />}
                title={item.title}
                description={item.detail}
                />
                <div>content</div>
            </Skeleton>
            </List.Item>
        )}
        />
    );
};
// 先定义组件 然后用connect去给这个组件好似外面包着一个父亲通过props传递
export default connect((state)=>{
    return {
        messageList:state.notice//返回的这个对象就是直接等价教给了props  其实props中就含有 {messageList:messageList}
    }
})(Message) ;

image.png

04-06:用户权限控制 角色管理

 用户的注册需要关联一个角色 是我们在注册的时候我们进行关联的  当你登录的时候 是以什么身份的登录的然后注册一下
 
 先分配好 
 
    权限名字-> 用户的权限代号-->用户名--->密码:超级管理员-->root-->大Boss-->666

    管理员-->  admin--->核心员工--->222
    
    打工仔--> worker--->打工仔--->333
    
 新建role组件,把权限名字和代号先存起来  这里存到了leanCloud我建立的一张表中userRole
     

权限组件样式:

import { Button, Checkbox, Form, Input ,Card,Col,Row} from 'antd';
import React,{useEffect,useState} from 'react';
import {history,useModel,useRequest} from "umi"
import {addRole} from "../../api/role"
const Role = () => {
    // 有请求,我们使用useRequest
    let {data,loading,error,run}=useRequest((values)=>{
        return addRole(values);
    },{
        manual:true
    })
    // 当你点击提交的时候会触发这个函数  values是表单帮你收集的他的子孩子中所有的value值
    const onFinish = (values) => {
        console.log('Success:', values);
        run(values)
        
    };
    return (
            <Form
                name="basic"
                labelCol={{
                    span: 6,
                }}
                wrapperCol={{
                    span: 12,
                }}
                onFinish={onFinish}
                autoComplete="off"
                >
                <Form.Item
                    label="权限名称"
                    name="rolename"
                    rules={[
                    {
                        required: true,
                        message: '请输入权限名称',
                    },
                    ]}
                >
                    <Input />
                </Form.Item>

                <Form.Item
                    label="权限代号"
                    name="role"
                    rules={[
                    {
                        required: true,
                        message: "请输入权限代号",
                    },
                    ]}
                >
                    <Input/>
                </Form.Item>
                <Form.Item
                    wrapperCol={{
                    offset: 8,
                    span: 16,
                    }}
                >
                    <Button type="primary" htmlType="submit">
                    提交
                    </Button>
                </Form.Item>
            </Form>
    );
};

export default Role;

image.png

api
    

image.png

结果:

image.png

注册用户组件样式:

import { Button, Form, Input,Select,Spin} from 'antd';
import React from 'react';
import {useRequest} from "umi"
import {getRole} from "../../api/role"
import {addUser} from "../../api/user"
const {Option} =Select
const UserManage = () => {
    // 有请求,我们使用useRequest
    let {data,loading,error}=useRequest(async()=>{
        let res= await getRole();
        // console.log("用户数据",res);
        return {data:res.results}
    })
    // 多个以上的useRequest 那你肯定要想办法解决这个命名冲突问题
    let addUserHandle = useRequest(async(values)=>{
        let res = await addUser(values)
        return {data:res}
    },{
        manual:true
    })
    // // 当你点击提交的时候会触发这个函数  values是表单帮你收集的他的子孩子中所有的value值
    const onFinish = (values) => {
        console.log('Success:', values);
        // 方法和数据都藏在了addUserHandle
        addUserHandle.run(values)
        // addUser(values).then((res)=>{
        //     console.log("添加成功",res);
        // })
    };
    return (
            <Spin spinning={loading}>
                    <Form
                    name="basic"
                    labelCol={{
                        span: 6,
                    }}
                    wrapperCol={{
                        span: 12,
                    }}
                    onFinish={onFinish}
                    autoComplete="off"
                    >
                    <Form.Item
                        label="用户名"
                        name="username"
                        rules={[
                        {
                            required: true,
                            message: '请输入用户名',
                        },
                        ]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="密码"
                        name="password"
                        rules={[
                        {
                            required: true,
                            message: "请输入密码",
                        },
                        ]}
                    >
                        <Input.Password/>
                    </Form.Item>
                    <Form.Item
                        label="关联权限代号"
                        name="connectRole"
                        rules={[
                        {
                            required: true,
                            message: "请选择关联的权限",
                        },
                        ]}
                    >
                        {/* 放的是选择框  当我们进行遍历的时候一定要考虑到前面这个属性是否真的有值 */}
                        <Select placeholder="请选择关联代号">
                            [
                                {
                                    data?.map((item)=>{
                                        return <Option value={item.role} key={item.objectId}>{item.rolename}</Option>
                                    })
                                }
                            ]
                        </Select>
                    </Form.Item>
                    <Form.Item
                        wrapperCol={{
                        offset: 8,
                        span: 16,
                        }}
                    >
                        <Button type="primary" htmlType="submit">
                        提交
                        </Button>
                    </Form.Item>
                </Form>
            </Spin>
    );
};

export default UserManage;
api

image.png

结果

image.png

04-06-01:流程

 首先我们来访用户还是先执行getInitialState获取全局数据 然后layout判断 但是现在呢我登录的账号信息有一个connectRole这个变量存的是这个账号有多大权限   
     access.js中会先拿到这个全局共享值根据这个变量来自定义一个权限变量
     
     所有的路由需要控制的添加一个access属性名 : 权限变量  
     
     意思就是 这个路由显示不显示先看access的值的布尔值  而这个值又是我们在access中定义并且通过全局用户信息
     来决定为真为假    真就显示 假就隐藏
     就这么简单
     
     

第一步:首先在src/access.js

// 动态生成权限数据  这个文件的这个值是getinitialState直接给我的
export default function(initialState){
    // 这个返回值的对象里面每一个键值对对应的都是一个权限 这个权限路由中要是有先去来这里看看会不会展示
    console.log("access权限文件",initialState);
    let {connectRole} = initialState
    return{
        // 为false 代表登录进来的不是前面的键名 为真代表此时是哪一个用户权限起作用了  必须是根据你登录的值来动态的呈现这个 只要你登录那么全局共享数据就能多一条关联角色
        isRoot:connectRole == "root",//只有当前登录的关联权限代号是root 我们把它变成真 否则为假
        isAdmin:connectRole == "root" || connectRole==="admin",//上级权限肯定能看下级的权限  所以我们采用或来表示这个权限能不能看
        isWorker:connectRole == "root" || connectRole==="admin"||connectRole==="worker",
    }
}

然后路由中添加access属性:权限变量就行了。 至此用户权限完全结束