React v16脱坑指南

347 阅读5分钟

获取Dom

import React, { Component } from 'react';

export default class Example extends Component {
  state = {
    dimensions: null,
  };

  componentDidMount() {
    this.setState({
      dimensions: {
        width: this.container.offsetWidth,
        height: this.container.offsetHeight,
      },
    });
  }

  renderContent() {
    const { dimensions } = this.state;

    return (
      <div>
        width: {dimensions.width}
        <br />
        height: {dimensions.height}
      </div>
    );
  }

  render() {
    const { dimensions } = this.state;

    return (
      <div className="Hello" ref={el => (this.container = el)}>
        {dimensions && this.renderContent()}
      </div>
    );
  }
}

线上部署刷新404

这个和vue是一样的问题,需要再nginx加入配置

 location /{
 	try_files $uri $uri/ /index.html;
 }

Error: EBUSY: resource busy or locked, lstat 'C:\hiberfil.sys'

今天遇到一个奇怪的报错,这个时候需要清空一下npm 缓存

方法一:删除 node_modules,然后再安装(npm i);
方法二:删除 node_modules,运行 npm cache clean 或者 npm cache clean --force 命令, 然后安装;
方法三:升级一下node版本,删除node_modules,然后再安装。

react 配置 stylus

1. 创建react项目

create-react-app react-demo

cd react-demo

2. 暴露config配置文件

先commit代码,如果不提交,执行以下指令执行会报错

npm run eject

3. 下载stylus相关包

npm install stylus stylus-loader --save-dev

4. 在config/webpack.config.js文件中配置

找到sass和css的配置

在原先的sass和css部分下面添加stylus配置

const stylusRegex =/\.styl$/;
const stylusModuleRegex=/\.module\.styl$/;

在 config/webpack.config.js 文件里

       { // 配置 stylus
          test: stylusRegex,
          exclude: stylusModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 2,
              sourceMap: isEnvProduction && shouldUseSourceMap,
              modules: true, // 设置模块化
            },
            'stylus-loader'
          ),
          sideEffects: true,
        },
        {
          test: stylusModuleRegex,
          use: getStyleLoaders(
            {
              importLoaders: 2,
              sourceMap: isEnvProduction && shouldUseSourceMap,
              modules: true,
              getLocalIdent: getCSSModuleLocalIdent,
            },
            'stylus-loader'
          ),
        },

4. 参考资料

create-react-app 配置 stylus

react配置装饰器

1.安装:

npm install @babel/plugin-proposal-decorators --save-dev

2. package.json配置:


"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      ["@babel/plugin-proposal-decorators", { "legacy": true }],
      ["@babel/plugin-proposal-class-properties", { "loose" : true }]
    ]

  }
}

3. 参考资料

装饰器connect问题:Error: The 'decorators' plugin requires a 'decoratorsBeforeExport' option, whose val...

react 使用props报错

报错:

 is missing in props validation  react/prop-types

1. 安装prop-types

npm i prop-types -S

2. 在页面中使用

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

setDate在componentDidMount周期报错

问题如下

Do not use setState in componentDidMount  react/no-did-mount-set-state

官网回答

解决

第一步,但是会提示componentDidMount有问题

    async componentDidMount () {
        const { default: component } = await importComponent();

        this.setState({
          component: component
        });
    }

第二部,在周期内部通过async 同步处理

    componentDidMount () {
      (async () => {
        const { default: component } = await importComponent();

        this.setState({
          component: component
        });
      })()
    }

使用 antd 组件报错Please update the following components

在index.js中注释调严格模式

报错就没有了

神坑,setdata函数不能保证是同步的

官方说明

给 setState 传递一个对象与传递一个函数的区别是什么?

传递一个函数可以让你在函数内访问到当前的 state 的值。因为 setState 的调用是分批的,所以你可以链式地进行更新,并确保它们是一个建立在另一个之上的,这样才不会发生冲突:

incrementCount() {
  this.setState((state) => {
    // 重要:在更新的时候读取 `state`,而不是 `this.state`。
    return {count: state.count + 1}
  });
}

handleSomething() {
  // 假设 `this.state.count` 从 0 开始。
  this.incrementCount();
  this.incrementCount();
  this.incrementCount();

  // 如果你现在在这里读取 `this.state.count`,它还是会为 0。
  // 但是,当 React 重新渲染该组件时,它会变为 3。
}

setState 什么时候是异步的?

目前,在事件处理函数内部的 setState 是异步的。

例如,如果 Parent 和 Child 在同一个 click 事件中都调用了 setState ,这样就可以确保 Child 不会被重新渲染两次。取而代之的是,React 会将该 state “冲洗” 到浏览器事件结束的时候,再统一地进行更新。这种机制可以在大型应用中得到很好的性能提升。

这只是一个实现的细节,所以请不要直接依赖于这种机制。在以后的版本当中,React 会在更多的情况下静默地使用 state 的批更新机制。

为什么 React 不同步地更新 this.state?

如前面章节解释的那样,在开始重新渲染之前,React 会有意地进行“等待”,直到所有在组件的事件处理函数内调用的 setState() 完成之后。这样可以通过避免不必要的重新渲染来提升性能。

但是,你可能还是会想,为什么 React 不能立即更新 this.state,而不对组件进行重新渲染呢。

主要有两个原因:

这样会破坏掉 props 和 state 之间的一致性,造成一些难以 debug 的问题。原因如下

这样会让一些我们正在实现的新功能变得无法实现。 这个 GitHub 评论 深入了该特殊示例。

我应该使用一个像 Redux 或 MobX 那样的 state 管理库吗?

或许需要。

在添加额外的库之前,最好先了解清楚 React 能干什么。你也可以只使用 React 来构建出一个比较复杂的应用。

配置全局请求拦截器

普通

import axios from 'axios';
import React, { Component } from 'react';
import webConfig from './web_config'
import ReactDOM from 'react-dom';

import { message, Spin } from 'antd';

// 设置请求路径
axios.defaults.baseURL = webConfig.rootUrl;
// axios.defaults.withCredentials = true;
axios.defaults.crossDomain = true; // 跨域配置
// 设置post请求头
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

// 当前正在请求的数量
let requestCount = 0

// 全局显示loading
function showLoading () {
  // requestCount为0,才创建loading, 避免重复创建
  if (requestCount === 0) {
    let dom = document.createElement('div')
    dom.setAttribute('id', 'loading')
    dom.style.position = 'fixed';
    dom.style.top = '0px';
    dom.style.left = '0px';
    dom.style.background = 'rgba(0, 0, 0, 0.4)';
    dom.style.display = 'flex';
    dom.style.justifyContent = 'center';
    dom.style.zIndex = '1000';
    dom.style.alignItems = 'center';
    dom.style.width = '100vw';
    dom.style.height = '100vh';
    document.body.appendChild(dom)
    ReactDOM.render(<Spin tip="数据请求中,请稍后..." delay={300} size="large"/>, dom)
  }
  requestCount++
}

// 隐藏loading
function hideLoading () {
  requestCount--
  if (requestCount === 0) {
    document.body.removeChild(document.getElementById('loading'))
  }
}

// 请求前拦截,请求中设置isLoading = false, 则可以取消全局请求loading
axios.interceptors.request.use((config) => {
  // 开启全局loading
  if (config.headers.isLoading !== false) {
    showLoading()
  }
  return config
}, (err) => {
  // 取消全局请求loading
  if (err.config.headers.isLoading !== false) {
    hideLoading()
  }
  return Promise.reject(err)
});

// 返回后拦截,请求中设置isLoading = false, 则可以取消全局请求loading
axios.interceptors.response.use((res) => {
  // 取消全局loading
  if (res.config.headers.isLoading !== false) {
    hideLoading()
  }
  return res
}, (err) => {
  // 取消全局loading
  if (err.config.headers.isLoading !== false) {
    hideLoading()
  }
  
  // 可以配置访问异常的回调提示
  if (err.message === 'Network Error') {
    message.warning('网络连接异常!')
  }
  
  return Promise.reject(err)
});

const $http = (url = '', data = {}, type = 'GET', _config = {}) => new Promise((resolve, reject) => {
  type = type.toUpperCase();
  
  const config = Object.assign(_config, {
    method: type,
    url: url
  });
  
  const timestamp = new Date().getTime();
  
  // get请求走正常
  if (['GET'].includes(type)) {
    config.params = data;
  } else { // post增加请求头
    Object.assign(config, {
      headers: {
        // 'Content-Type': 'multipart/form-data', // form 格式
        'Content-Type': 'application/json', // json 格式
        'token': localStorage.getItem('token')
      }
    });
    
    // 封装Data => FormData
    // const formdata = new FormData();
    // for (let key in data) {
    //   formdata.append(key, data[key]);
    // }
    // config.data = formdata;
    
    config.data = data;
  }
  
  axios(config).then((response) => {
    // 失败
    if (response && (response.data.code !== 200)) {
      console.error('接口->请求未成功,接口:', response.config.url, '传参报错,参数:', JSON.parse(crypto.decrypt(JSON.parse(response.config.data).data)));
      message.warning(response.data.msg);
      resolve(response && response.data);
    } else {
      resolve(response && response.data);
    }
  })
    .catch((err) => {
      if (err.response) {
        console.error('接口', err.config.url, '服务端报错,报错码->', err.response.status.toString(), '参数->', JSON.parse(err.config.data));
        message.warning(err.response.data.msg);
  
      } else {
        console.error('接口', err.config.url, '服务器无响应,网络链接异常',  JSON.parse(err.config.data));
        message.warning(err.response.data.msg);
      }
      reject(err);
    });
});

// formData 转 Json
let convert_FormData_to_json2 = (formData) => {
  let objData = {};
  formData.forEach((value, key) => objData[key] = value);
  return JSON.stringify(objData);
};

export const $getData = async (url, data, _config = {}) => {
  let res = await $http(url, data, 'GET', _config)
  if (res.code === 200){
    return Promise.resolve(res)
  } else {
    message.warning(res.data.msg);
    return Promise.reject(res)
  }
  // return Promise.resolve(res)
};

export const $postData = async (url, data, _config = {}) => {
  let res = await $http(url, data, 'POST', _config)
  if (res.code === 200){
    if (res.data) { // 当有返回才解析
      res.data = JSON.parse(crypto.decrypt(res.data));
    }
    return Promise.resolve(res)
  } else {
    message.warning(res.data.msg);
    return Promise.reject(res)
  }
  // return Promise.resolve(res)
};

配合AES加密

这里的Crypto文件可以在我的另外一篇文章里获取从零开始构建前后端AES加密通信

import axios from 'axios';
import React, { Component } from 'react';
import webConfig from './web_config'
import ReactDOM from 'react-dom';
import crypto from './Crypto';

import { message, Spin } from 'antd';

// 设置请求路径
axios.defaults.baseURL = webConfig.rootUrl;
// axios.defaults.withCredentials = true;
axios.defaults.crossDomain = true; // 跨域配置
// 设置post请求头
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

// 当前正在请求的数量
let requestCount = 0

// 全局显示loading
function showLoading () {
  // requestCount为0,才创建loading, 避免重复创建
  if (requestCount === 0) {
    let dom = document.createElement('div')
    dom.setAttribute('id', 'loading')
    dom.style.position = 'fixed';
    dom.style.top = '0px';
    dom.style.left = '0px';
    dom.style.background = 'rgba(0, 0, 0, 0.4)';
    dom.style.display = 'flex';
    dom.style.justifyContent = 'center';
    dom.style.zIndex = '1000';
    dom.style.alignItems = 'center';
    dom.style.width = '100vw';
    dom.style.height = '100vh';
    document.body.appendChild(dom)
    ReactDOM.render(<Spin tip="数据请求中,请稍后..." delay={300} size="large"/>, dom)
  }
  requestCount++
}

// 隐藏loading
function hideLoading () {
  requestCount--
  if (requestCount === 0) {
    document.body.removeChild(document.getElementById('loading'))
  }
}

// 请求前拦截,请求中设置isLoading = false, 则可以取消全局请求loading
axios.interceptors.request.use((config) => {
  // 开启全局loading
  if (config.headers.isLoading !== false) {
    showLoading()
  }
  return config
}, (err) => {
  // 取消全局请求loading
  if (err.config.headers.isLoading !== false) {
    hideLoading()
  }
  return Promise.reject(err)
});

// 返回后拦截,请求中设置isLoading = false, 则可以取消全局请求loading
axios.interceptors.response.use((res) => {
  // 取消全局loading
  if (res.config.headers.isLoading !== false) {
    hideLoading()
  }
  return res
}, (err) => {
  // 取消全局loading
  if (err.config.headers.isLoading !== false) {
    hideLoading()
  }
  
  // 可以配置访问异常的回调提示
  if (err.message === 'Network Error') {
    message.warning('网络连接异常!')
  }
  
  return Promise.reject(err)
});

const $http = (url = '', data = {}, type = 'GET', _config = {}) => new Promise((resolve, reject) => {
  type = type.toUpperCase();
  
  const config = Object.assign(_config, {
    method: type,
    url: url
  });
  
  const timestamp = new Date().getTime();
  
  // get请求走正常
  if (['GET'].includes(type)) {
    // config.params = data;
    config.params = {}
    config.params.data = crypto.encrypt(JSON.stringify(data));
    config.params.timestamp = timestamp.toString()
  } else { // post增加请求头
    Object.assign(config, {
      headers: {
        // 'Content-Type': 'multipart/form-data',
        'Content-Type': 'application/json',
        'token': localStorage.getItem('token')
      }
    });
    
    // 封装Data => FormData
    // const formdata = new FormData();
    // for (let key in data) {
    //   formdata.append(key, data[key]);
    // }
    // config.data = formdata;
    config.data = {}
    config.data.data = crypto.encrypt(JSON.stringify(data));
    config.data.timestamp = timestamp.toString();
    console.log('解密参数', JSON.parse(crypto.decrypt(config.data.data)))
  }
  
  axios(config).then((response) => {
    // 失败
    if (response && (response.data.code !== 200)) {
      console.error('接口->请求未成功,接口:', response.config.url, '传参报错,参数:', JSON.parse(crypto.decrypt(JSON.parse(response.config.data).data)));
      message.warning(response.data.msg);
      resolve(response && response.data);
    } else {
      resolve(response && response.data);
    }
  })
    .catch((err) => {
      if (err.response) {
        console.error('接口', err.config.url, '服务端报错,报错码->', err.response.status.toString(), '参数->', JSON.parse(err.config.data));
        message.warning(err.response.data.msg);
  
      } else {
        console.error('接口', err.config.url, '服务器无响应,网络链接异常',  JSON.parse(err.config.data));
        message.warning(err.response.data.msg);
      }
      reject(err);
    });
});

// formData 转 Json
let convert_FormData_to_json2 = (formData) => {
  let objData = {};
  formData.forEach((value, key) => objData[key] = value);
  return JSON.stringify(objData);
};

export const $getData = async (url, data, _config = {}) => {
  let res = await $http(url, data, 'GET', _config)
  if (res.code === 200){
    if (res.data) {// 当有返回才解析
      res.data = JSON.parse(crypto.decrypt(res.data));
    }
    return Promise.resolve(res)
  } else {
    message.warning(res.data.msg);
    return Promise.reject(res)
  }
  // return Promise.resolve(res)
};

export const $postData = async (url, data, _config = {}) => {
  let res = await $http(url, data, 'POST', _config)
  if (res.code === 200){
    if (res.data) { // 当有返回才解析
      res.data = JSON.parse(crypto.decrypt(res.data));
    }
    return Promise.resolve(res)
  } else {
    message.warning(res.data.msg);
    return Promise.reject(res)
  }
  // return Promise.resolve(res)
};