日常开发小技巧

237 阅读3分钟

1.加速播放语音

// A文件:
import React, { PureComponent, Fragment } from 'react';
import { Button } from 'antd';
import ReactAudioPlayer from 'react-audio-player';

export default class AudioPlayer extends PureComponent {
  handleSpeed=(val) => {
    this.rap.audioEl.playbackRate = val || 1;
    this.rap.audioEl.play();
  }
  render() {
    const { src } = this.props;
    return (
      <Fragment>
        <ReactAudioPlayer ref={(element) => { this.rap = element; }} src={src} controls />
        <div style={{ display: 'inline-block', position: 'relative' }}>
          <Button style={{ marginLeft: 5 }} size="small" onClick={() => this.handleSpeed(1)}>1X</Button>
          <Button style={{ marginLeft: 5 }} size="small" onClick={() => this.handleSpeed(1.2)}>1.2X</Button>
          <Button style={{ marginLeft: 5 }} size="small" onClick={() => this.handleSpeed(1.5)}>1.5X</Button>
          <Button style={{ marginLeft: 5 }} size="small" onClick={() => this.handleSpeed(2)}>2X</Button>
        </div>
      </Fragment>
    );
  }
}

B文件:
import AudioPlayer from 'components/Audio/AudioPlayer';
 <Row style={{ marginBottom: 10, paddingLeft: 50 }}>
          <AudioPlayer src={model.recordUrl} />
</Row>

2.权限组件

// 组件:
import { PureComponent } from 'react';
import { connect } from 'dva';

@connect(({ user }) => ({
  permission: user.permission,
  isAdmin: user.currentUser.superAdmin,
}))
export default class Auth extends PureComponent {
  render() {
    const { id, permission, isAdmin } = this.props;
    const hasPermission = isAdmin || !!permission[id];

    if (process.env.NODE_ENV === 'development') {
      return typeof this.props.children === 'function' ? this.props.children(true) : this.props.children;
    }

    // support render props
    // https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce
    if (typeof this.props.children === 'function') {
      return this.props.children(hasPermission);
    }

    return hasPermission ? this.props.children : null;
  }
}

// rademe.md
jsx
<Auth id="authname">
  <Button>xxx</Button>
</Auth>

或

<Auth id="authname">
{
  (hasAuth) => {
    return hasAuth ? <Button>xxx</Button> : <span>没有权限</span>
  }
}
</Auth>
// A文件:
import Auth from "components/Auth";
 <Auth id="后端返回的权限码">
     <a onClick={() => listSelect(record)}>跟进</a>
 </Auth>

3.table列表-columns


const bizTypeOptions = [{ value: '1', text: 'C1' }, { value: '2', text: 'C2' },
  { value: '6', text: '客服' }];


// 时间格式转化
  transTime = (value) => {
    const date = new Date(value);
    const Y = date.getFullYear();
    let m = date.getMonth() + 1;
    let d = date.getDate();
    let H = date.getHours();
    let i = date.getMinutes();
    let s = date.getSeconds();
    if (m < 10) {
      m = `0${m}`;
    }
    if (d < 10) {
      d = `0${d}`;
    }
    if (H < 10) {
      H = `0${H}`;
    }
    if (i < 10) {
      i = `0${i}`;
    }
    if (s < 10) {
      s = `0${s}`;
    }
    const t = `${Y}-${m}-${d} ${H}:${i}:${s}`;
    return t;
  };
  
  
[
    {
        title: "序号",
        key: "id",
        // eslint-disable-next-line no-mixed-operators
        render: (text, record, index) => (
          <span>{index + 1 + 10 * (pageIndex - 1)}</span>
        ),
      },
     {
        title: '接通率',
        key: 'callSuccessRatio',
        dataIndex: 'callSuccessRatio',
        align: 'center',
        width: 100,
        render: val => (`${val}%`),
      },
       {
        title: '排班开始日期',
        dataIndex: 'startDate',
      }, 
       { title: '价格',
        width: 200,
        render: (row) => {
          return (
            <div>
              <p>底价{row.floorPrice}售价{row.salePrice}<br />展板价{row.exhibitionPriceMin}-{row.exhibitionPriceMax}<br />金融{row.financialPriceMin}-{row.financialPriceMax}</p>
            </div>
          );
        },
      },
      {
        title: "绑定时间",
        dataIndex: "operateTime",
        key: "operateTime",
        render: (text, record) => (
          <span>
            {record.operateTime ? this.transTime(record.operateTime) : ""}
          </span>
        ),
      },
       {
      title: '是否外呼',
      dataIndex: 'isDial',
      render: (text, item) => (
        <span>{item.isDial === true ? '是' : '否'}</span>
      ),
       },
      {
        title: "是否启用",
        dataIndex: "enabled",
        render: (h) => {
          return <span>{h ? "是" : "否"}</span>;
        },
      },
       {
        title: '业务类型',
        dataIndex: 'bizType',
        render: val => <span>{bizTypeOptions.find(x => x.value === val) ? bizTypeOptions.find(x => x.value === val).text : ''}</span>,
      },
       {
      title: '当前状态',
      dataIndex: 'enabled',
      render: enabled => (enabled === 1 ? '已启用' : '已关闭'),
      },
      {
      title: '车源编号',
      render: (record) => {
        return (
          <span>
            <a onClick={() => this.listSelect(record)}>{record.carCode}</a>
          </span>
        );
      },
    }, 
       {
        title: '操作',
        fixed: 'right',
        align: 'center',
        width: 120,
        render: (item) => {
          return (
            <span>
              <a onClick={this.handleEdit.bind(this, item)}>编辑</a>
              <a onClick={this.cancelItem.bind(this, item)} style={{ marginLeft: 10 }}>删除</a>
            </span>
          );
        },
      }
  ]
  
  
  
  
    <Col md={8} sm={8}>
            <FormItem label="业务线">
              {getFieldDecorator('bizType')(
                <Select allowClear placeholder="请选择" style={{ width: '100%' }}>
                  {bizTypeOptions.map(x => <Option key={x.value}>{x.text}</Option>)}
                </Select>
              )}
            </FormItem>
   </Col>
   
    <Col {...ColLayout}>
          <FormItem label="是否本地" {...FormItemLayout}>
            {getFieldDecorator('bizSubType')(
              <Select placeholder="请选择" allowClear>
                <Option value={1} key={1}>异地购</Option>
                <Option value={2} key={2}>本地购</Option>
              </Select>
            )}
          </FormItem>
        </Col>
          

4.utils小方法

手机号中间四位加星

/**
 * 手机号中间四位加星
 * @param {string} tel
 */
export function getRemainPhone(tel) {
  if (!tel || typeof tel !== 'string') {
    return '';
  }
  return tel.replace(/-/g, '').replace(/(\d{3})\d*(\d{4})/, '$1****$2');
}


A文件:
<span>电话:{getRemainPhone(dealerMobile)} </span>

字符串拆成数组

// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id']
function urlToList(url) {
  const urllist = url.split('/').filter(i => i);
  return urllist.map((urlItem, index) => {
    return `/${urllist.slice(0, index + 1).join('/')}`;
  });
}


// 示例:
function urlToList(url) {
  const urllist = url.split('/').filter(i => i);
  return urllist.map((urlItem, index) => {
    return `/${urllist.slice(0, index + 1).join('/')}`;
  });
}

urlToList('/userinfo/2144/id')
(3) ['/userinfo', '/userinfo/2144', '/userinfo/2144/id']

是否是电话号

  IsTelNumber(number) {
    return /^(0\d{2}\d{8}(-\d{1,4})?)|(0\d{3}\d{7,8}(-\d{1,4})?)|1[3|4|5|6|7|8|9][0-9]{9}$/.test(number);
  },
  
  import { isTelNumber } from "utils/check";
  
  <Input
     placeholder="请填写服务号码"
     style={{ width: 250 }}
     onBlur={this.handleCheckExist}
   />
              
  
 handleCheckExist = () => {
    const number = getFieldValue("number");
    if (isTelNumber(number)) {.....}
   
 }

手机号验证

/*
 *手机验证
 */
IsPhoneNumber(number) {
  const phoneRexp = new RegExp(/^1[3|4|5|6|7|8|9][0-9]{9}$/);
  if (number === '') {
    return '';
  } else {
    return phoneRexp.test(number);
  }
},

# A文件:
import { isPhoneNumber } from 'utils/check';

   <Col md={8} sm={24}>
              <FormItem {...formItemLayout} label={fieldLabels.TelePhone}>
                {getFieldDecorator("telephone", {
                  rules: [
                    {
                      required: true,
                      message: "请输入正确的手机号",
                      validator: (rule, value, callback) => {
                        if (isPhoneNumber(value)) {
                          callback();
                        } else {
                          callback(rule.message);
                        }
                      },
                    },
                  ],
                  valuePropName: "text",
                })(<PhoneEye />)}
              </FormItem>
     </Col>


或者在一些弹窗蒙层中校验后再做ajax操作

confirm({
    title: '修改手机号',
    content: Content,
    onOk: (close) => {
      if (!isPhoneNumber(mobilePhone)) {
        Modal.error({
          title: '错误',
          content: '请您输入正确的11位手机号码',
        });
        return false;
      }
      this.props.dispatch({
        type: 'xxxxxx,
        payload: {
         ......
        },
      }).then((response) => {
     
      });

      return false;
    },
  });

姓名验证

 /*
  *姓名验证
  */
 IsName(number) {
   const nameRexp = new RegExp(/^[\u4e00-\u9fa5]{2,8}$/);// 2-8位汉字
   if (number === '') {
     return '';
   } else {
     return nameRexp.test(number);
   }
 },

登录密码验证

  /*
   *登录密码验证
   *正则表达式验证(数字+字母)或者(数字+特殊字符)或者(字母+特殊字符),不能是纯数字、纯字母、纯特殊字符,即只要符合这3个组合其中之一都为true
   */
  IsPassword(number) {
    const passwordRexp = new RegExp(/^((?=.*[a-zA-Z])(?=.*\d)|(?=[a-zA-Z])(?=.*[#@!~%^&*])|(?=.*\d)(?=.*[#@!~%^&*]))[a-zA-Z\d#@!~%^&*]{6,20}$/);
    if (number === '') {
      return '';
    } else {
      return passwordRexp.test(number);
    }
  },

年龄是否满足成年人

  /**
   *年龄是否满足成年人
   * @param {number} number
   */
  IsAdult(number) {
    if (number === '') {
      return '';
    } else if (number >= 18 && number <= 100) {
      return true;
    } else {
      return false;
    }
  },
};

元转换万元

// 元转换万元
export const wanYuan = (value) => !isNaN(Number(value) / 10000) && `${(Number(value) / 10000).toFixed(2)}万元`;


# A文件使用:
import { wanYuan } from "../../utils";

<Col span={8} style={colStyle}>
  新车指导价: {details && details.riskApply && details.riskApply.newVehiclePrice && wanYuan(details.riskApply.newVehiclePrice)}
</Col>

获取cookie中value

export function getCookie(name) {
    var value = "; " + document.cookie;
    var parts = value.split("; " + name + "=");
    if (parts.length == 2) return parts.pop().split(";").shift();
}


# A文件:
import {getCookie, getQueryString} from '../utils';

const phone = getCookie('phone');

获取浏览器地址参数 问号后面

// 获取浏览器地址参数 问号后面
export function getQueryStringQuestion(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = window.location.href.split('?')[1].match(reg)
    if (r != null) return decodeURI(r[2]);
    return null;
}

A文件:
import { getQueryStringQuestion } from "../../utils";

instanceId: getQueryStringQuestion("id") || "",

获取选项值

//获取选项值
export function getValue(name, key, value) {
    let result = undefined;
    if (name) {
        for (let i in name) {
            if (value) {
                result = name[key][value];
            } else {
                result = name[key];
            }
        }
    }
    return result;
}

import { getValue } from '../utils';
金融来源:<span>{getValue(formdata.approval, 'padFundedFrom')}</span>

示例:
var aaa={'name':'zhangsan',data:'1'}
getValue(aaa,'name') // 张三

5.踩坑手记

踩坑手记

- antd组件默认属性(例如initialValue、defaultValue、defaultFileList)只会被初始化一次),直接修改不会重新渲染,如果需要重新渲染则需要修改另外的属性值(例如setFieldsValue、value、fileList)
- setState直接修改数组或者对象的某个值是不会导致渲染的(深复制,浅复制的问题)代码入下:
```javascript
//❌
//获取父组件的数据
  	var arr = this.state.tags;
	//子组件中传来的数据数组
        for(var i=0; i<rows.length; i++){

	    //isAddData方法是判断数据存不存在
            if(this.isAddData(arr, rows[i])){
                arr.unshift(rows[i]);
            }

        }

        this.setState({ tags: arr });
//✅
var arr = this.state.tags;
	//子组件中传来的数据数组
        for(var i=0; i<rows.length; i++){

	    //isAddData方法是判断数据存不存在
            if(this.isAddData(arr, rows[i])){
                arr.unshift(rows[i]);
            }

        }

        this.setState({ tags: [...arr] });
  • 同一个页面多个子组件调用同一个接口,数据可能会导致出错问题

    解决方案: 用 state 定义一个 name: 设置 true/false,等待第一个接口加载完毕,再进行 第二次请求(建议设计组件的时候数据可以传入)

  • 同一个接口需要点击多次,以最后一次请求为准,因为没有用 loading 才会 出现的数据回来了但是界面没有刷新数据

    解决方案:fetch 添加一个 type:’takeLatest’,(takeEvery(默认),takeLatest,throttle,watcher)代码如下:

effects: {
    fetch: [function* ({payload}, { call, put }) {
      yield put({
        type: 'changeLoading',
        payload: true,
      });
      const response = yield call(calendarMonth,payload);
      
      if(response){
        yield put({
          type: 'save',
          payload: response,
        });
      }
    }, {type: 'takeLatest'}]
}

注意: watcher是指传入的任务函数就是一个watcher直接fork就好 throttle还要传入一个ms配置,这个ms代表着在多少毫秒内只触发一次同一类型saga任务 而takeEvery是不会限制同一类型执行次数 takeLatest只能执行一个同一类型任务,有执行中的再次执行就会取消

  • 一个页面由多个组件组成 如何清空表单 如果通过emit分别触发各个组件,是可以实现的,但感觉代价太大

    解决方案:不需要emit,this.props.dispatch(),dispatch 是根据你里面设置的type内容 然后转发到指定的model的,所以你这边 要设置正确以后,在model那边才能接收到你发送的这条action,model中reducers 处理数据(同步),effects接收数据(异步逻辑都要在effects里),subscriptions 监听数据,代码如下


this.props.dispatch({
  type: 'global/clearNotices',
  payload: type,
});


//models
export default {
  namespace: 'global',
  reducers: {
    clearNotices(state, action) {
      return {
        ...state,
        list: action.payload,
      };
    },
  },
};
  
  • 单页面应用不要使用location.href这种方式会导致整页刷新,写类似a的跳转可以使用Link组建,如果js控制跳转,代码如下:
import { routerRedux } from 'dva/router';

this.props.dispatch(routerRedux.push({
    pathname: '/user/register-result',
    state: {
      account,
    },
  }));
  • 一个页面由多个组件组成,跨组件校验问题现在是通过Modal弹窗自己实现的 ,当组件保存按钮在别的组件里,需要带标识实现,实现特别复杂费劲(任务跟进是这样实现的)

  • TABLE翻页 selectRow一定要清空

  • 页面级的数据尽量不放store中,放在本文件state中,这种类似于session的 会带来bug

  • 变量在不通层级中尽量不要重复,析构时候会指向不明

  • this问题 箭头函数this是成员this,普通函数this指向函数本身,可以在钩子函数中赋值给自定义的_this

  • route.js定义的组件中不要遗忘model的配置,否则dispatch的type的model没有配置则无法生效 代码如下:

//dynamicWrapper(app,[model],() =>import('router路径'))
const routerConfig = {
    '/': {
      component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
    },
  • render中改state容易死循环

    解决方案:建议只有在componentWillMount,componentDidMount,componentWillReceiveProps方法中可以修改state值,在componentWillUpdate和componentDidUpdate中修改也可能导致无限循环调用

开发建议

  • 文件拆分和建文件夹归类 有的文件好几千行代码
  • 文件组件化,现在很多文件都是为了实现自己的功能
  • 根据是否需要高度的复用性,把组件划分为 Dumb 和 Smart 组件,约定俗成地把它们分别放到 components 和 containers 目录下。