React项目实战小结

409 阅读5分钟

1.代理服务器解决跨域问题:

例如:项目启动http://locallhost:3000,而请求的地址http://locallhost:5000

解决方案:

react-clipackage.json中添加"proxy": "http://localhost:5000"设置代理服务器对请求进行转发

2.post请求参数的处理:

axios默认是使用json格式的请求体携带参数数据

但是后端对于post请求大多还是类似于name=tom&pwd=123这种格式

所以这里需要对数据进行统一;

来看下github中的axios如何处理:github.com/axios/axios

  • 引入qs了,然后调用qs.stringify对参数对象进行格式化处理即可

项目中最好使用axios请求、响应拦截器进行处理(Interceptors)

3.axios拦截器的简单使用:

🚀请求拦截器:

  • post请求发送前对参数的处理(qs.stringify(data))

🚀响应拦截器:

  • 对请求回包的数据提前进行统一进行错误处理
  • 简化相应结果的获取
//axios.js
// 封装能发ajax的请求的函数
import axios from "axios";
import qs from "qs";
import {message} from "antd";
//🚀 请求拦截器 在发送请求数据之前,提前对数据进行处理
axios.interceptors.request.use(function (config) {
    console.log("我是拦截器",config);
    const {method,data} = config;
    if(method === "post" && typeof data === "object"){
        //🚀 将通过qs处理过的数据复制给config.data
        config.data = qs.stringify(data);
    }
    return config;
}, function (error) {
    return Promise.reject(error);
});
//🚀 响应拦截器
axios.interceptors.response.use(function (response) {
    // 返回response.data后,请求的结果就不用再添加.data进行获取了
    return response.data;
}, function (error) {
    message.error("请求失败了"+ error.message);
    // 🚀 让错误出于pending状态,不再往下面进行
    return new Promise(()=>{})
    // return Promise.reject(error);
});
export default axios;

4.侧面动态菜单栏相关:

  • 首先是在React生命周期componentWillMount组件加载前,将菜单配置的内容存到this.menuNodes
    • 如果有children会生成SubMenu
  • defaultSelectedKeys:一级路由就是默认选中的菜单项目
  • defaultOpenKeys:这个是二级路由选中后,刷新浏览器后,二级路由打开的配置
    • 路由地址与当前菜单的某个子菜单的key匹配,如果匹配上了,那么让这个匹配上的item的key赋值给this.openkeys
import React, {Component} from 'react';
import {Layout, Menu} from "antd";
import {Link,withRouter} from "react-router-dom";
import Logo from "../../assets/images/logo.png";
import "./index.less";
import menuConfig from "../../config/menuConfig";
const { Sider,} = Layout;
const { SubMenu } = Menu;
class LeftNav extends Component {

    componentWillMount() {
        // 会被加载一次 在render之前执行
        console.log("我执行啦")
        this.menuNodes = this.getMenuNodes(menuConfig);
        console.log(this.menuNodes);
    }
    //根据指定的菜单数据产生<Menu.Item>的节点
    //如果有children会生成SubMenu
    //map + 递归
    getMenuNodes = (menuList) => {
        // 🚀 当前路由地址path
        const path  = this.props.location.pathname;
        return  menuList.map((item)=>{
                console.log("我是menuitem",item);
                if(!item.children){
                    //生成Menu.item
                    return(
                        <Menu.Item key={item.key} icon = {item.icon}>
                            <Link to={item.key}>
                                {item.title}
                            </Link>
                        </Menu.Item>
                    )
                }else{
                    //需求:刷新后默认打开二级菜单
                    //🚀 如果当前那请求路由地址与当前菜单的某个子菜单的key匹配,将父菜单的key保存到openkey
                const cItem = item.children.find(cItem => cItem.key===path);
                    if(cItem){
                        this.openKeys = item.key;
                    }
                    return(
                        <SubMenu key={item.key} icon = {item.icon} title={item.title}>
                            {/* 🚀这里面用到了递归操作*/}
                            {
                                this.getMenuNodes(item.children)
                            }
                        </SubMenu>
                    )
                }
            })
    }
    render() {
      // 🚀 一级路由刷新后选中样式丢失的问题
        let defaultKeys = this.props.location.pathname;
        return (
            <Sider trigger={null} collapsible collapsed={this.props.collapsed}>
                <div className="logo">
                    <Link className="left-nav-link" to="/">
                        <img src={Logo}/>
                        <h1>海贼王管理平台</h1>
                    </Link>
                    <Menu
                        defaultSelectedKeys={[defaultKeys]}
                        defaultOpenKeys={[this.openKeys]}
                        mode="inline"
                        theme="dark"
                    >
                        {
                            this.menuNodes
                        }
                    </Menu>
                </div>
            </Sider>
        );
    }
}

export default withRouter(LeftNav);

5.antd4.x中input封装动态赋值的问题:

需求场景:现在想要封装一个input框,在点击列表的某个项目时,将项目name带过去,传递给封装的input框,动态赋值并且可以修改; 再一个就是需要在父组件拿到子组件的封装好的input的值。(更多关于antd3.x迁移到4.x请看ant-design.gitee.io/components/…)

  • initialValue={this.props.categoryName}:初始化值。
  • UNSAFE_componentWillReceiveProps中在每次变化的时候更新input中的categoryName的值。
import React, {Component} from 'react';
import {Form,Input} from "antd";
class AddUpdateForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            categoryName:""
        }
    }
    formRef = React.createRef();
    // shouldComponentUpdate(nextProps, nextState, nextContext) {
    //     this.formRef.current.setFieldsValue({
    //         categoryName: nextProps.categoryName
    //     })
    //     return true;
    // }
    UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
            console.log("我是分类的nextProps",nextProps);
            this.formRef.current.setFieldsValue({
                categoryName: nextProps.categoryName
            })
    }
    render() {
        return (
            <Form initialValues={{remember:true}}
            onFinish={this.onFinish}
                  ref={this.formRef}
            >
                <Form.Item
                    name="categoryName"
                    rules={[
                        {
                           required:true,
                            message:"品类名为必填项目~!"
                        }
                    ]}
                    initialValue={this.props.categoryName}
                >
                    <Input type="text" placerholder="请输入分类名称" onChange={this.handleChange}/>
                </Form.Item>
            </Form>
        );
    }
}
export default AddUpdateForm;
  • this.formRef.current.formRef.current.getFieldValue("categoryName"):通过两层ref,在父组件中获取子组件的值。

// Category.js

import React, {Component} from 'react';
import {Card,Button,Table,message,Modal} from "antd";
import { reqCategorys,reqUpdateCategory,reqAddCategory} from "../../api/index";
import AddUpdateForm from "./add-update-form";
class Category extends Component {
    formRef = React.createRef();
    
    handleOk = () => {
        // 🚀 form表单验证
        console.log("我是表单的名字啊",this.formRef.current.formRef.current.getFieldValue("categoryName"))
        console.log("form",this.formRef)
        //做修改的时候showStatus = 2;

        //做添加的时候showStatus = 1
    }
    render() {
        return (
            <Card
                extra = {extra}
            >
                <Modal title={this.state.showStatus=== 1 ? "添加分类":"显示分类"} visible={this.state.showStatus!==0 } onOk={this.handleOk} onCancel={this.handleCancel}>
                  //🚀 这里也添加了ref
                    <AddUpdateForm ref={this.formRef} categoryName={this.state.currentName}>{category.name}</AddUpdateForm>
                </Modal>
            </Card>
        );
    }
}

export default Category;

6.antd中upload上传

antd中的上传<Upload/>组件需要注意下,onChange函数的三个参数filefileListevent

因为上传后filefileList中的图片文件的名字不一致,为了后续开发方便,需要将两个名字设置成一致的。

如下图的handleChange函数为onChange事件绑定的方法:

 // 🚀 上传图片时触发的回调函数
    handleChange = ({file, fileList }) => {
        this.setState({ fileList });
        console.log("我是当前上传到图片file",file,fileList)
        if(file.status === "done"){
            //将数组的最后一个file保存到file变量中
            file = fileList[fileList.length -1];
            //取出响应数据中的图片的url和文件名
            const {name,url} = file.response.data;
            //保存到上传的file对象
            file.name = name;
            file.url = url;
        }
        console.log("fileList",fileList);
    }

7.父组件向子组件传递props时,子组件显示props为undefined

当在父组件中通过异步行为获取应用数据,可能会导致传给子组件的数据为空值。这里给出两种解决方案:

    // 这个方法在render方法之前调用,初始化的render不会被触发
    componentWillReceiveProps(nextProps, nextContext) {
        console.log("我是下一个props、",nextProps);
        const imgs = nextProps.imgs;
        if(imgs && imgs.length > 0){
            const fileList = imgs.map((imgName,i)=>{
                return {
                    uid:-i,
                    name:imgName,
                    status:"done",
                    url:"http://localhost:5000/upload/" + imgName
                }
            });
            this.setState({fileList});
        }
    }

    static getDerivedStateFromProps(nextProps,prevState){
        //该方法内禁止访问this
        if(nextProps.imgs && nextProps.imgs.length > 0){
            //通过对比nextProps和prevState,返回一个用于更新状态的对象
            const fileList = nextProps.imgs.map((imgItem,i)=>{
                return {
                    uid:-i,
                    name:imgItem,
                    status:"done",
                    url:"http://localhost:5000/upload/" + imgItem
                }
            })
           // 🚀 这里返回的对象相当于是在其他地方调用了一次this.setState()
            return {
                fileList
            }
        }
        //不需要更新状态,返回null
        return null
    }
  • getDerivedStateFromProps直接返回一个对象,在其他地方调用了一次this.setState()

未完待续...