React 项目精进技巧

2022-08-15 08:33:39 浏览数 (1)

1、工程化实践

umi dva作为底层框架,Ant Design Mobile为 UI 组件库,是蚂蚁金服推崇的的react项目最佳实践,具有国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面等等方便一线开发者的功能,部分功能代码可以参考Ant Design pro,目录结构预览如下

2、通用组件

  • ErrorBoundary

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界:错误边界 – React

  • 懒加载
代码语言:javascript复制
// index.js
import React, { Component, lazy, Suspense } from 'react';

export default class Index extends Component {

  constructor(props) {
    super(props);
    this.state = {

    };
  }

  _renderLazy = () => {
    let Lazy;
    const { component, delay, ...other } = this.props;
    if (!component || component.constructor.name !== 'Promise') {
      Lazy = lazy(() => import('./error'));
    } else {
      Lazy = lazy(() => {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(component);
          }, delay || 300);
        })
      });
    }
    
    return <Lazy {...other} />
  }

  render() {
    return (
      <div>
        <Suspense fallback={<div>loading...</div>}>
          {this._renderLazy()}
        </Suspense>
      </div>
    )
  }
}
代码语言:javascript复制
// error.js
import React, { Component } from 'react';

export default class Error extends Component {

  constructor(props) {
    super(props);
    this.state = {

    };
  }

  render() {
    return (
      <div>
        组件引入错误!
      </div>
    )
  }
}

3、vs-code插件

  • project-tpl

比如输入func则可自动生成hook模板

4、自定义 hook

  • 请求hook封装

对发送请求封装成hook后十分整洁,还可以监听数据变化自动发送请求

效果:

代码语言:javascript复制
  const [houses, loading] = useHttpHook({
    url: '/house/search',
    body: {
      ...page,
      houseName,
      code: query?.code,
      startTime: query?.startTime   ' 00:00:00',
      endTime: query?.endTime   ' 23:59:59'
    },
    watch: [page.pageNum, houseSubmitName]
  });

实现细节:

代码语言:javascript复制
// utils/http.js
import { Toast } from 'antd-mobile';

export default function Http({
  url,
  method = 'post',
  headers,
  body = {},
  setLoading,
  setResult,
}){
  setLoading && setLoading(true);

  const defaultHeader = {
    'Content-type': 'application/json'
  };

  let params;
  if(method.toUpperCase() === 'GET'){
    params = undefined;
  }else {
    params = {
      headers: {
        ...defaultHeader,
        headers
      },
      method,
      body: JSON.stringify(body)
    }
  }

  return new Promise((resolve, reject)=>{
    fetch('/api'   url, params)
      .then(res => res.json())
      .then(res => {
        if(res.status === 200){
          resolve(res.data);
          setResult && setResult(res.data);
        }else {
          Toast.fail(res.errMsg);
          reject(res.errMsg);
        }
      })
      .catch(err => {
        Toast.fail(err);
        reject(err);
      })
      .finally(() => {
        setLoading && setLoading(false);
      })
  });
}
代码语言:javascript复制
// useHttpHook.js
import { useState, useEffect } from 'react';
import { Http } from '@/utils';

export default function useHttpHook({
  url,
  method = 'post',
  headers,
  body = {},
  watch = []
}) {
  const [result, setResult] = useState();
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    Http({
      url,
      method,
      headers,
      body,
      setResult,
      setLoading
    });
  }, watch);

  return [result, loading];
}
  • 图片懒加载hook

图片在可视区域才加载真实图片

代码语言:javascript复制
import { useEffect } from 'react';
import { isEmpty } from 'project-libs';

/**
 * 1,监听图片是否进入可视区域;
 * 2,将src属性的值替换为真实的图片地址,data-src
 * 3,停止监听当前的节点
 * @param {*} ele 监听的img元素类名,如 .imgBox
 * @param {*} callback 
 * @param {*} watch 监听的变化数据,如data变化后触发此hook逻辑
 */
let observer;
export default function useImgHook(ele, callback, watch = []){
  useEffect(()=>{
    const nodes = document.querySelectorAll(ele);
    if(!isEmpty(nodes)){
      observer = new IntersectionObserver((entries)=>{
        callback && callback(entries);
        entries.forEach(item => {
          // console.log(item)
          if(item.isIntersecting){
            const dataSrc = item.target.getAttribute('data-src');
            item.target.setAttribute('src', dataSrc);
            observer.unobserve(item.target);
          }
        });
      });
      nodes.forEach(item => {
        observer.observe(item);
      });
    }

    return () => {
      if(!isEmpty(nodes) && observer){
        observer.disconnect();
      }
    }
  }, watch)
}

使用例子

代码语言:javascript复制
useImgHook('.' styles.goodImg, (entries:any) => { console.log('entries', entries) }, [tabs, activeTab, categories])

 
<img className={styles.goodImg} src={require('@/assets/h5/blank.png')} data-src={item['smallImage']} alt=""/>
  • 滚动加载hook

滚动到底部再加载数据

代码语言:javascript复制
import { useEffect } from 'react';

let observer;
export default function useObserverHook(ele, callback, watch = []) {
  useEffect(() => {
    const node = document.querySelector(ele);
    if (node) {
      observer = new IntersectionObserver(entries => {
        callback && callback(entries);
      });
      observer.observe(node);
    }

    return () => {
      if (observer && node) {
        // 解绑元素
        observer.unobserve(node);

        // 停止监听
        observer.disconnect();
      }
    }
  }, watch);
}

使用示例

代码语言:javascript复制
  /**
   * 1,监听loading是否展示出来;
   * 2,修改分页数据;
   * 3,监听分页数据的修改,发送接口,请求下一页的数据;
   * 4,监听loading变化,拼装数据
   */
  useObserverHook('#'   CommonEnum.LOADING_ID, (entries) => {
    // console.log(entries)
    if (!loading && entries[0].isIntersecting) {
      setPage({
        ...page,
        pageNum: page.pageNum   1
      });
    }

  }, null);
  • 骨架屏

替换loading页,显示加载中的页面骨架,给用户更好的浏览体验

src目录下建skeleton文件夹

写骨架屏的静态文件页面,如下

代码语言:javascript复制
import React, { useState, useEffect } from 'react';
import ItemBox from '@/components/ItemBox/ItemBox';
// @ts-ignore
import Arrow from '@/assets/h5/arrow.png';
import styles from './index.less'

export default function(props){
  const [state, setState] = useState(Array(3).fill(1))

  useEffect(() => {
    console.log(state)
  }, [])

  return (
    <div className={styles.orderListsSkeletons}>
      {state.map((item, index) => {
        return <div key={index} className={styles.itemBox}>
          <ItemBox
            name={name}
            smallImage={require('./../../assets/h5/blank.png')}
            desc={[`-- 金币`]}
            stock={'--'}
            style={{ backgroundColor: '#F7F7F7' }}
            actions={<img src={Arrow} className={styles.button} />}
          />
        </div>

      })}
    </div>
  )
}

之后在没有数据时展示这个骨架屏即可

代码语言:javascript复制
data ?  <Components /> : <OrderListSkeletons />

5、通用工具函数

  • 时间格式化
代码语言:javascript复制
import dayjs from 'dayjs';

export default function timer(time, type='all'){
  return dayjs(time).format(type === 'all' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
  • project-libs

是一个常用函数集锦的工具库,包括浏览器、函数式、常用验证、cookie、数组处理等函数

project-libs

0 人点赞