react+ts+antdesign 踩坑实录

4,090 阅读5分钟

首次尝试使用react,用react+ts+antdesign做了个项目,初学者的坑都踩了遍,将项目过程中的踩坑记录了下来,不得不感叹react hook真的好用诶。貌似多写点介绍才能显示在缩略图中哦,强迫症迫使我啰嗦加字滴答滴答

antdesign 嵌套子表格获取异步数据

1.初步方案:在 expandedRowRender 中请求

// 子表单部分
  const expandedRowRender = (record: detailParams) => {
    let {errorMsg, errorOther, errorType} = record
    let data = {
      projectName,
      errorMsg,
      errorOther,
      errorType
    }
    
    getErrorListDetail(data).then((da: any) => {
      if (da.bizSuccess) {
        da.dataList.forEach((v: any, index: number) => {
          v.key = index
        })
        setSubDataSource(da.dataList)
      } else {
        setError(da.msg || '获取数据失败')
      }
    }).catch(err => {
      setError(err)
    })
    
    const columns = [
      {
        title: '错误类型',
        dataIndex: 'errorType',
        key: 'errorType',
        className: 'column-center',
        width: 200
      }
    ]
    
    return <div>
      {error ? (<Alert message={error} type="success" closable afterClose={() => setError(null)} />) : null}
      
      <Table columns={columns} dataSource={subDataSource} pagination={false} />
    </div>
  };

存在的问题

点击一次加载子表格后,会不断发起请求,造成死循环

原因

expandedRowRender 是在 Table 组件的 render 方法中调用的,React render 中用调用异步请求 getErrorListDetail 会造成重复调用的问题,getErrorListDetail -> setState -> render -> getErrorListDetail -> setState -> render,循环往复

2.优化方案

将请求写在 onExpend 中,渲染写在 expandedRowRender

const onExpand = (expanded: boolean, record: detailParams) => {
    if (!expanded) {
      // 如果不断的添加键值对,会造成数据过于庞大,浪费资源,
      // 在每次合并的时候讲相应键值下的数据清空
    } else {      
      let {errorMsg, errorOther, errorType} = record
      let data = {
        projectName,
        errorMsg,
        errorOther,
        errorType
      }

     getErrorListDetail(data).then((da: any) => {
        if (da.bizSuccess) {
          da.dataList.forEach((v: any, index: number) => {
            v.key = index
          })
          setSubDataSource(da.dataList)
        } else {
          setError(da.msg || '获取数据失败')
        }
      }).catch(err => {
        setError(err)
      })
    }
 }

存在的问题

每次点击展开,请求当前行的数据,会修改其他行的嵌套子表单数据

原因

3.最终方案

给每行一个对应的key: rowKey={record => record.key}

const onExpand = (expanded: boolean, record: detailParams) => {
    if (!expanded) {
      // 如果不断的添加键值对,会造成数据过于庞大,浪费资源,
      // 在每次合并的时候讲相应键值下的数据清空
      const data = {
        ...subDataSourceObj,
        [record.key]: []
      }
      setSubDataSourceObj(data)
    } else {      
      let {errorMsg, errorOther, errorType} = record
      let data = {
        projectName,
        errorMsg,
        errorOther,
        errorType
      }

      getErrorListDetail(data).then((da: any) => {        
        const {bizSuccess, dataList, msg} = da
        if (bizSuccess) {
          dataList.forEach((v: any, index: number) => {
            v.key = index
          })
          const data = {
            ...subDataSourceObj,
            [record.key]: dataList
          }
          setSubDataSourceObj(data)
        } else {
          setError(msg || '获取数据失败')
        }
      }).catch(err => {
        setError(err)
      })
    }
  }

子表展示时以 subDataSourceObj[record.key] 取值

antdesign日期组件,默认为英文国际版

根据项目需求,需要中文版

import moment from 'moment'
import 'moment/locale/zh-cn'
// 要放在所有引入的最后面
moment.locale('zh-cn')

<RangePicker
    // 设置为中文
    locale={zhCN}
    showTime={{ format: 'HH:mm' }}
    format="YYYY-MM-DD HH:mm"
    onChange={(e, value) => {onTimeChange(value)}}
  />

此时安装moment需要注意,用npm安装的momentantd,只有年会变成中文,因此需要用yarn add安装这两个依赖

命令yarn add antd moment,问题解决

关于中英文

  • 默认antdesign也是英文,需要在index.tsx文件中加上中文转换
import cn from 'antd/es/locale/zh_CN';

ReactDOM.render( 
  <ConfigProvider locale={cn}>
      <App />
  </ConfigProvider>,
  document.getElementById('root')
);

项目打包后的路径问题

1.打包后的静态资源加载不到,都是404

  • 解决方案
    • package.json中增加一行代码:"homepage": "."

2.在生产环境刷新页面报404

  • 原因
    • 使用BroswerRouter在生产环境刷新会有问题
  • 解决方案
    • 改用hashHistory

虽然browserHistory实现的功能更多, browserHistory 使用的是 HTML5 的 History API,浏览器提供相应的接口来修改浏览器的历史记录;而 hashHistory 是通过改变地址后面的 hash 来改变浏览器的历史记录;History API 提供了 pushState() 和 replaceState() 方法来增加或替换历史记录。而 hash 没有相应的方法,所以并没有替换历史记录的功能。但 react-router 通过 polyfill 实现了此功能,好像是使用 sessionStorage。 另一个原因是 hash 部分并不会被浏览器发送到服务端,也就是说不管是请求 #foo 还是 #bar ,服务只知道请求了 index.html 并不知道 hash 部分的细节。而 History API 需要服务端支持,这样服务端能获取请求细节。 还有一个原因是因为有些应该会忽略 URL 中的 hash 部分,记得之前将 URL 使用微信分享时会丢失 hash 部分。

但在不需要太多功能的情况下,我们完全可以使用hashHistory

react hook 基础知识笔记

状态提升

  • 在react中,任何可变数据理应只有一个单一数据源,在应用中要保持自上而下的数据流
  • 多个组件共享同一数据,需要把这个数据提升到最近的父组件中进行管理
  • 与双向数据流的区别
    • 不是尝试在不同组件中同步状态,而是在数据源处对数据做综合处理

react hook

effect hook

  • 无需清除的副作用
// useEffect参数是纯函数时,每次渲染(render)后都会执行
useEffect(() => {})
  • 需要清除的副作用
    // 返回函数里,可以清除
    // click事件的清除
    useEffect(() => {
        document.addEventListener('click', console.log(1))
        return () => {
            document.removeEventListener('click')
        }
    })
    

控制effect的执行(第二个参数)

// useEffect第二个参数是空数组时,代表只执行一次
useEffect(() => {}, [])

// useEffect第二个参数数组里是变量值时,代表只在该变量变化时执行
useEffect(() => {
	console.log(like)
}, [like])

HOC对比自定义hook的缺陷

HOC:higher order component 高阶组件

参数是组件,返回值也是组件

自定义hook

多次被调用的(请求等)可以封装到函数中,单独提取出来

useRef

  • 修改ref的值不会引发组件的render
const likeRef = useRef(0)
console.log(likeRef.current)
  • 常用用法———获取DOM节点

useContext

在祖先组件和后代组件之间,兄弟组件之间,共享值的方法(避免组件树一层层传递)

hook规则

  • 只在最顶层使用Hook,不在循环或者嵌套函数中调用
  • 只在react函数或自定义hook中调用hook,不能在js函数中调用hook

其他Hook

[文档连接]usehooks.com/

  • useReducer 用于组件传值
  • useCallback 用于性能调优