React+antdPro4+TS(typescript)相关(知识点)踩坑记录

最近公司新开了项目,是后台管理系统,在技术选型上选择的时候,选择了react,然后整体的框架选择的是antd pro@4+这个脚手架搭建出来的单页面应用,要是想要仔细了解antd pro 可以看一下他的官方文档:antd pro,此次开发整体的技术栈包括:antd@4.7.3,react@16.8.6,管理数据流采用的是dva@2.4.0,另外还应用了umi@3.2.0,前端后端的数据交互,用的是umi-request,这个umi-request本质上来说是fetch,而目前特别流行的前后端交互工具axios,本质上来说还是对原生XMLHttpRequest的封装,所以两者的在实际开发上是略有不同,具体可以去查看各自的官方API;话不多说,直接看踩坑点吧:

1.antd相关

  • antd@4版本的表单

antd@4与3版本略有不同,少了一个getFieldDecorator,3版本绑定表单项如下:

      <FormItems
        className={styles['formItem-wrapper'] + ' ' + className}
        style={{ marginBottom: marginBottom }}
        label={label}
        labelCol={{ span: 24 }}
        wrapperCol={wrapperCol}
      >
        {getFieldDecorator(`${keyId}`, {
          rules: rules,
          initialValue
        })(children)}
      </FormItems>

4版本的相对3版本来说极大的简化了,一些初始值和校验规则,可以直接放到FormItems中去,代码如下:

        <FormItem
          {...formItemLayout}
          label='资料编辑方式'
          name="editType"
          rules={[
            {
              required: true,
              message: '请选择资料编辑方式',
            },
          ]}
        >
          <Radio.Group options={editTypeArr} onChange={onRadioChange} value={curRadio} />
        </FormItem>

若是要给表单设置初始值,可以在Form中设置initialValues,部分代码如下:

      <Form
        requiredMark
        style={{ marginTop: 8 }}
        form={form}
        name="basic"
        initialValues={data.stepOneObj}
      >
      </Form>
  • antd@4树形控件(Tree)

可以单独定义每个节点要显示的内容,具体部分代码如下:

      <Tree
        draggable
        autoExpandParent
        onDrop={this.onDrop}
        expandedKeys={expandedKeys}
        switcherIcon={<DownOutlined />}
        onSelect={this.onSelect}
        onExpand={this.onExpand}
      >
        {treeData.length > 0 ? this.genTreeNode(treeData) : this.EmptyNode()}
      </Tree>

渲染节点genTreeNode具体的方法:

  genTreeNode = (treeData: Array<any>) => {
    return treeData.map((item: TreeNodeItem) => {
      if (item.children && item.children.length > 0) {
        return (
          <TreeNode key={item.key} title={this.renderTitle(item)}>
            {this.genTreeNode(item.children)}
          </TreeNode>
        )
      }
      return (
        <TreeNode key={item.key} title={this.renderTitle(item)} />
      )
    })
  }

注意看一下渲染title对应的方法,实现了自定义节点显示的需求

  renderTitle = (node: TreeNodeItem) => {
    const { isEdit, inputVal, level = 1 } = node

    if (isEdit) {
      return (
        <div className='wrap-tree-title'>
          <Input
            maxLength={30}
            placeholder='请填写名称'
            value={inputVal}
            onFocus={(e) => {e.stopPropagation()}}
            onBlur={(e) => {e.stopPropagation()}}
            onChange={throttle((e) => this.refreshNode(e, node, 'change'),500, { leading: true, trailing: false })}/>
          <div className='wrap-btn'>
            <Button type="link" onClick={(e) => this.refreshNode(e, node, 'save')}>保存</Button>
            <Button type="link" onClick={(e) => this.refreshNode(e, node, 'cancel')}>取消</Button>
          </div>
        </div>
      )
    }
    return (
      <div className='wrap-tree-title'>
        <div className='title'>{node.title}</div>
        <div>
          <Popover content={this.renderAddBtn(node)}>
            {
              level < 4 && (
                <Button
                  type="link" shape="circle"
                  icon={<PlusOutlined />}
                  onClick={(e) => e.stopPropagation()}/>
              )
            }
          </Popover>
          <Button
            type="link" shape="circle"
            icon={<FormOutlined />}
            onClick={(e) => this.refreshNode(e, node, 'edit')}/>
          <Button
            type="link" shape="circle"
            icon={<CloseOutlined />}
            onClick={(e) => this.refreshNode(e, node, 'delete')}/>
        </div>
      </div>
    )
  }

自定义节点的显示效果如下图:

还有一个,就是要实现节点拖拽对应的方法,我们产品这边的需求是,节点最多可以设置4级,超过4级的,就不显示加号,使其无法增加子节点,在做拖拽这里的时候,就是每次拖拽完以后,都要改变一下树形节点的level,改变一下树形节点的层级,这样就达到目的了,具体代码如下:

  onDrop = info => {
    console.log(info)
    const dropKey = info.node.props.eventKey
    const dragKey = info.dragNode.props.eventKey
    const dropPos = info.node.props.pos.split('-')
    const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])

    const loop = (data, key, callback) => {
      for (let i = 0; i < data.length; i++) {
        if (data[i].key === key) {
          return callback(data[i], i, data)
        }
        if (data[i].children) {
          loop(data[i].children, key, callback)
        }
      }
    }
    const data = [ ...this.state.treeData ]

    // Find dragObject
    let dragObj
    loop(data, dragKey, (item, index, arr) => {
      arr.splice(index, 1)
      dragObj = item
    })

    if (!info.dropToGap) {
      // Drop on the content
      loop(data, dropKey, item => {
        item.children = item.children || []
        // where to insert 示例添加到尾部,可以是随意位置
        item.children.push(dragObj)
      })
    } else if (
      (info.node.props.children || []).length > 0 && // Has children
      info.node.props.expanded && // Is expanded
      dropPosition === 1 // On the bottom gap
    ) {
      loop(data, dropKey, item => {
        item.children = item.children || []
        // where to insert 示例添加到头部,可以是随意位置
        item.children.unshift(dragObj)
      })
    } else {
      let ar
      let i
      loop(data, dropKey, (item, index, arr) => {
        ar = arr
        i = index
      })
      if (dropPosition === -1) {
        ar.splice(i, 0, dragObj)
      } else {
        ar.splice(i + 1, 0, dragObj)
      }
    }
    this.updateArr(data)
    this.setState({
      treeData: data,
    })
  };

 

2.react相关

注意:componentWillReceiveProps is deprecated since React 16.9.0,注意在使用类组件的时候,部分生命周期方法已经废弃酌情使用

本次用Hook用的比较多,用Hook基本上就能满足所有需求了,经常用的有:useState,useEffect(最基础的方法,再次就不在赘述了)

另外还有umi这个框架封装好的一些hook、比如:useParams,直接把路由上的参数给取下来,详见代码:

import { useParams } from 'umi'

//在hook中就直接这样子来使用,就可以把路由中对应的参数取下来了
const { id } = useParams<{id:string}>()

 

3.umi-request

前后端交互的一个工具,具体可以参见github地址:umi-request,umi-request本质上来说是fetch,而目前特别流行的前后端交互工具axios,本质上来说还是对原生XMLHttpRequest的封装,所以两者的在实际开发上是略有不同,在本次开发的过程中有一个问题一直困扰了我很久,就是对后台返回的响应做统一的处理,一旦返回中有响应错误,就走catch方法,不走then方法,后来想到了一个解决方案,记录一下:

  • 建立文件夹

  • 文件夹中的代码分享:
/**
 * request 网络请求工具
 * 更详细的 api 文档: https://github.com/umijs/umi-request
 */
import { extend } from 'umi-request'
import Cookies from 'js-cookie'
import { notification, message } from 'antd'
import { logout } from '@/models/login'

const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
}

/**
 * 异常处理程序
 */
export const errorHandler = (error: { response: Response }): Response => {
  const { response } = error
  message.destroy()
  if (!response) {
    notification.error({
      description: '您的网络发生异常,无法连接服务器',
      message: '网络异常',
    })
    return response
  }

  if (response && response.status) {
    const errorText = codeMessage[response.status] || response.statusText
    const { status, url } = response
    notification.error({
      message: `请求错误 ${status}: ${url}`,
      description: errorText,
    })
  }
  return response
}

/**
 * 配置request请求时的默认参数
 */
const request = extend({
  errorHandler, // 默认错误处理
  credentials: 'include', // 默认请求是否带上cookie
})
/**
 * 对请求统一处理
 */
request.interceptors.request.use((url: any, options: any) => {
//在请求头设置token,每个接口都统一设置
  options.headers.Authorization = Cookies.get('tokenPartner')
  return options
})
/**
 * 对响应统一处理
 */
request.interceptors.response.use(async response => {
  const contentType = response.headers.get('content-type')
    //此时返回的数据是二进制流,不是json,因此做特殊处理
  if (contentType === 'application/octet-stream') {
    return response
  }
  // 将接口返回的数据格式化成json
  // const res = await response.clone().json()
  return response
})

function handleResError(err: any) {
  message.destroy()
  message.error(err.message)
  const code = err?.errCode
  // token过期,重新登录
  if (code === 10110002) {
    logout()
  }
}

function wrapRequest(url: string, newOptions: any) {
  return new Promise((resolve) => {
    request(url, { ...newOptions }).then(res => {
      if (res?.type === 'application/octet-stream') {
        resolve(res)
        return
      }
      if (!res?.success) {
        const msg = res?.errmsg || res?.errMsg
        // eslint-disable-next-line @typescript-eslint/no-throw-literal
        throw { ...res, message: msg }
        return
      }
      resolve(res)
    }).catch(err => {
        //这一步很关键,就是要对响应出错做统一的处理,官方提供的errorHandler是对服务器出错做统一处理,而不是后台返回的响应出错做统一的处理
      handleResError(err)
    })
  })
}

//把封装好的抛出去
export default wrapRequest
  • 在实际的业务代码中调用封装好的umi-request:

  • 以上图框起来的getList为例子:一旦后台返回的响应有错误,就不会走这里的then方法了,直接被wrapRequest这个方法中的catch给捕获住,从而达到了对响应错误做统一处理的目的;

 

4.dva相关

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

具体详见dva官方文档:dva介绍

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值