前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 项目精进技巧

React 项目精进技巧

作者头像
EchoROne
发布2022-08-15 08:33:39
9470
发布2022-08-15 08:33:39
举报
文章被收录于专栏:玩转大前端玩转大前端

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、工程化实践
  • 2、通用组件
  • 3、vs-code插件
  • 4、自定义 hook
  • 写骨架屏的静态文件页面,如下
  • 5、通用工具函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档