EXDestroyer

EXDestroyer 查看完整档案

深圳编辑深圳大学  |  软件工程 编辑腾讯科技(深圳)有限公司  |  WEB前端开发 编辑填写个人主网站
编辑

Deviant, Meditative, Subterranean

个人动态

EXDestroyer 收藏了文章 · 2019-09-18

用了这么久HTTP, 你是否了解Content-Length?

摘要: 理解HTTP协议...

由Content-Length导致的问题引发的一系列思考:
前段时间开发API网关, 使用postman调试时出现了超时的情况, 经排查确定是请求数据被处理后Content-Length与实际不一致导致的问题, 故有此文.

Content-Length, HTTP消息长度, 用十进制数字表示的八位字节的数目. 一般情况下, 很多工作都被框架完成, 我们很少去关注这部分内容, 但少数情况下发生了Content-Length与实际消息长度不一致, 程序可能会发生比较奇怪的异常, 如:

  • 无响应直到超时.
  • 请求被截断, 而且下一个请求解析出现错乱.

Content-Length是HTTP消息长度, 用十进制数字表示的八位字节的数目, 是Headers中常见的一个字段. Content-Length应该是精确的, 否则就会导致异常 (特别地, HTTP1.0中这个字段可有可无).

Content-Length首部指示出报文中实体主体的字节大小. 这个大小是包含了所有内容编码的, 比如, 对文本文件进行了gzip压缩的话, Content-Length首部指的就是压缩后的大小而不是原始大小.

Content-Length是如何工作的

Content-Length使用十进制的数字表示了消息的长度, 服务端/客户端通过它来得知后续要读取消息的长度.

如果这个长度不正确, 会发生如下情况:

Content-Length > 实际长度

如果Content-Length比实际的长度大, 服务端/客户端读取到消息结尾后, 会等待下一个字节, 自然会无响应直到超时.

同样地, 在响应消息中Content-Length超过实际长度也是一样的效果:

Content-Length < 实际长度

如果这个长度小于实际长度, 首次请求的消息会被截取, 比如参数为param=piaoruiqing, Content-Length为10, 那么这次请求的消息会被截取为: param=piao, 如图所示:

但, 仅仅是如此吗, 当然不, 我们再来看看第二次请求会发生什么让人意外的事情, 如图:

连续的两次请求, 第一次消息被截断, 而第二次没有发生预期的截断, 而是服务端抛出了异常: Request method 'ruiqingPOST' not supported.刺不刺激 (ノ)゚Д゚( )

ruiqingPOST是个什么神仙方法??? 此时, 凭着多年开发(DEBUG)经验练就的敏感度, 我们大致可以猜出, 上一次请求被截取剩下的消息, 在这次请求出现了. 掏出wireshark来验证一下, 如图:

导致这种情况的原因就是开启了Connection:keep-alive, 如果使用Connection:close, 所产生的现象就是每一次的请求都被截断, 但不会产生解析混乱(如将上一次剩下的消息拼接到后续的请求消息中).

不确定Content-Length的值怎么办

Content-Length首部指示出报文中实体主体的字节大小. 但如在请求处理完成前无法获取消息长度, 我们就无法明确指定Content-Length, 此时应该使用Transfer-Encoding: chunked

什么是Transfer-Encoding: chunked

数据以一系列分块的形式进行发送. Content-Length 首部在这种情况下不被发送. 在每一个分块的开头需要添加当前分块的长度, 以十六进制的形式表示,后面紧跟着 \r\n , 之后是分块本身, 后面也是\r\n. 终止块是一个常规的分块, 不同之处在于其长度为0.

Transfer-Encoding: chunked是如何工作的

接下来我们用一个下载文件的例子, 来探讨Transfer-Encoding: chunked是如何工作的. 服务端代码如下:

使用postman发起请求, wireshark抓包查看, 如图:

在wireshark中可以很清晰地看到chunked的数据, 其结构大致是: 返回的消息被分为多个数据块, 每个数据块有两部分, 长度 + 数据, 这两部分都以CRLF(即\r\n)结尾. 而终止块是一个特殊的数据块, 其长度为0, 如图:

如此, 即完成了分块编码. 其主要应用于如下场景, 即要传输大量的数据, 但是在请求在没有被处理完之前响应的长度是无法获得的. 例如, 当需要用从数据库中查询获得的数据生成一个大的HTML表格、需要传输大量的图片等.

  • Content-Length如果存在且生效, 必须是正确的, 否则会发生异常.(大于实际值会超时, 小于实际值会截断并可能导致后续的数据解析混乱)
  • 如果报文中包含Transfer-Encoding: chunked首部, 那么Content-Length将被忽略.

参考

版权声明

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接:https://blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com.

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用

查看原文

EXDestroyer 收藏了文章 · 2019-08-27

Fundebug录屏插件更新至0.6.0

摘要: 录屏插件的性能进一步优化,传输的数据体积大幅度减少。

录屏功能介绍

Fundebug提供专业的异常监控服务,当线上应用出现 BUG 的时候,我们可以第一时间报警,帮助开发者及时发现 BUG,提高 Debug 效率。在网页端,我们通过原创的录屏技术,可以 100%还原 BUG 出现之前用户的操作流程,帮助开发者快速复现出错场景。演示视频如下:https://static.fundebug.cn/el...

其实,我们录制的并不是一个真正的视频!算法经过优化,整个“录制”过程 CPU 的使用率非常低。和传统的视频相比,体积小了成百上千倍。Fundebug 插件“录制”的“短视频”,压缩后的体积只有几十 KB。

感兴趣的话,欢迎大家免费试用~

尊重用户隐私

录屏功能涉及到用户隐私,我们作为第三方服务,也非常重视这一点:

  • Fundebug 默认关闭录屏功能,开发者需要的时候可以自行开启;
  • Fundebug 并不是全程录屏,只会录制 BUG 出现之前 10~20s 的用户操作;
  • Fundebug 提供敏感信息过滤过滤功能,开发者可以过滤掉用户隐私信息;
  • Fundebug 重视数据安全,传输过程全程加密,数据库有多重安全防护
  • Fundebug 会定期(目前是删除 60 天之前的数据)删除过期错误数据,这样既节省成本,也保护用户隐私;

请大家放心使用~

录屏插件更新至 0.6.0

此次插件升级包括两个方面:

性能升级
我们一直在对插件进行优化,不断地提升插件的性能。此次更新我们对核心算法做了改进,将其中比较耗费计算时间和内存资源的正则匹配改为字符串匹配(KMP)算法;并且缓存中间数据来省去大量重复的计算;最后,我们还对数据的编码做了优化,同等体积数据相比之前减少 1/4。

新增 revedioVersion 字段
为了便于用户弄清楚当前插件的版本,进而顺利对插件进行升级,我们在发送的数据中新增录屏插件版本(revedioVersion)字段。该信息在报错详情的右侧展示。

接入方法

从 BUG 监控插件 1.7.3版本开始,我们拆分了录屏代码。如果需要使用录屏功能的话,需要单独接入录屏插件。

  • 使用 Script 方式接入

    <script
        type="text/javascript"
        data-original="https://js.fundebug.cn/fundebug.revideo.0.6.0.min.js"
    />
  • 使用 NPM 接入

    require("fundebug-revideo");

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用

版权声明

转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2019/08/27/release_revedio_0_6_0/

查看原文

EXDestroyer 赞了回答 · 2019-06-28

解决js高斯模糊算法问题

高斯模糊有两种方案做:

  • 直接用二维公式进行二重循环,复杂度为O(xy(2r)^2)
  • 用一维公式分别对x、y循环,复杂度为O(2xy(2r))

测试结果:

  • 用二重循环:(500*800,20) 4566ms
  • 分别循环:(500*800,20) 237ms

可以发现刚好差20倍左右,也就是radius模糊半径的值

结果图:

图片描述
图片描述
图片描述

实现代码如下:

代码较长,建议移步到我的博客看代码

html:

html<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>test</title>
    <script data-original="GaussianBlur.js"></script>
</head>
<body>
    <img data-original="images/test3.jpg" alt="img source" id="imgSource">
    <canvas id="canvas"></canvas>
</body>
</html>

javascript:

  • gaussBlur : 二重循环
  • gaussBlur1 : 分别循环
javascript/**
 * Created by zhaofengmiao on 15/3/22.
 */
window.onload = function(){
    var img = document.getElementById("imgSource"),
        canvas = document.getElementById('canvas'),
        width = img.width,
        height = img.height;

    // console.log(width);

    canvas.width = width;
    canvas.height = height;

    var context = canvas.getContext("2d");
    context.drawImage(img, 0, 0);

    var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);

    //console.log(canvasData);

    // 开始
    var startTime = +new Date();

    var tempData = gaussBlur(canvasData, 20);


    context.putImageData(tempData,0,0);

    var endTime = +new Date();
    console.log(" 一共经历时间:" + (endTime - startTime) + "ms");
}

/**
 * 此函数为二重循环
 */
function gaussBlur(imgData, radius, sigma) {
    var pixes = imgData.data,
        width = imgData.width,
        height = imgData.height;

    radius = radius || 5;
    sigma = sigma || radius / 3;

    var gaussEdge = radius * 2 + 1;    // 高斯矩阵的边长

    var gaussMatrix = [],
        gaussSum = 0,
        a = 1 / (2 * sigma * sigma * Math.PI),
        b = -a * Math.PI;

    for (var i=-radius; i<=radius; i++) {
        for (var j=-radius; j<=radius; j++) {
            var gxy = a * Math.exp((i * i + j * j) * b);
            gaussMatrix.push(gxy);
            gaussSum += gxy;    // 得到高斯矩阵的和,用来归一化
        }
    }
    var gaussNum = (radius + 1) * (radius + 1);
    for (var i=0; i<gaussNum; i++) {
        gaussMatrix[i] = gaussMatrix[i] / gaussSum;    // 除gaussSum是归一化
    }

    //console.log(gaussMatrix);

    // 循环计算整个图像每个像素高斯处理之后的值
    for (var x=0; x<width;x++) {
        for (var y=0; y<height; y++) {
            var r = 0,
                g = 0,
                b = 0;

            //console.log(1);

            // 计算每个点的高斯处理之后的值
            for (var i=-radius; i<=radius; i++) {
                // 处理边缘
                var m = handleEdge(i, x, width);
                for (var j=-radius; j<=radius; j++) {
                    // 处理边缘
                    var mm = handleEdge(j, y, height);

                    var currentPixId = (mm * width + m) * 4;

                    var jj = j + radius;
                    var ii = i + radius;
                    r += pixes[currentPixId] * gaussMatrix[jj * gaussEdge + ii];
                    g += pixes[currentPixId + 1] * gaussMatrix[jj * gaussEdge + ii];
                    b += pixes[currentPixId + 2] * gaussMatrix[jj * gaussEdge + ii];

                }
            }
            var pixId = (y * width + x) * 4;

            pixes[pixId] = ~~r;
            pixes[pixId + 1] = ~~g;
            pixes[pixId + 2] = ~~b;
        }
    }
    imgData.data = pixes;
    return imgData;
}

function handleEdge(i, x, w) {
    var  m = x + i;
    if (m < 0) {
        m = -m;
    } else if (m >= w) {
        m = w + i - x;
    }
    return m;
}

/**
 * 此函数为分别循环
 */
function gaussBlur1(imgData,radius, sigma) {
    var pixes = imgData.data;
    var width = imgData.width;
    var height = imgData.height;
    var gaussMatrix = [],
        gaussSum = 0,
        x, y,
        r, g, b, a,
        i, j, k, len;


    radius = Math.floor(radius) || 3;
    sigma = sigma || radius / 3;

    a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
    b = -1 / (2 * sigma * sigma);
    //生成高斯矩阵
    for (i = 0, x = -radius; x <= radius; x++, i++){
        g = a * Math.exp(b * x * x);
        gaussMatrix[i] = g;
        gaussSum += g;

    }
    //归一化, 保证高斯矩阵的值在[0,1]之间
    for (i = 0, len = gaussMatrix.length; i < len; i++) {
        gaussMatrix[i] /= gaussSum;
    }
    //x 方向一维高斯运算
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            r = g = b = a = 0;
            gaussSum = 0;
            for(j = -radius; j <= radius; j++){
                k = x + j;
                if(k >= 0 && k < width){//确保 k 没超出 x 的范围
                    //r,g,b,a 四个一组
                    i = (y * width + k) * 4;
                    r += pixes[i] * gaussMatrix[j + radius];
                    g += pixes[i + 1] * gaussMatrix[j + radius];
                    b += pixes[i + 2] * gaussMatrix[j + radius];
                    // a += pixes[i + 3] * gaussMatrix[j];
                    gaussSum += gaussMatrix[j + radius];
                }
            }
            i = (y * width + x) * 4;
            // 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
            // console.log(gaussSum)
            pixes[i] = r / gaussSum;
            pixes[i + 1] = g / gaussSum;
            pixes[i + 2] = b / gaussSum;
            // pixes[i + 3] = a ;
        }
    }
    //y 方向一维高斯运算
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
            r = g = b = a = 0;
            gaussSum = 0;
            for(j = -radius; j <= radius; j++){
                k = y + j;
                if(k >= 0 && k < height){//确保 k 没超出 y 的范围
                    i = (k * width + x) * 4;
                    r += pixes[i] * gaussMatrix[j + radius];
                    g += pixes[i + 1] * gaussMatrix[j + radius];
                    b += pixes[i + 2] * gaussMatrix[j + radius];
                    // a += pixes[i + 3] * gaussMatrix[j];
                    gaussSum += gaussMatrix[j + radius];
                }
            }
            i = (y * width + x) * 4;
            pixes[i] = r / gaussSum;
            pixes[i + 1] = g / gaussSum;
            pixes[i + 2] = b / gaussSum;
            // pixes[i] = r ;
            // pixes[i + 1] = g ;
            // pixes[i + 2] = b ;
            // pixes[i + 3] = a ;
        }
    }
    //end
    imgData.data = pixes;
    return imgData;
}

关注 12 回答 3

EXDestroyer 评论了文章 · 2019-01-09

从 loading 的 9 种写法谈 React 业务开发

banner

前言

这是一篇比较全面讲解 React 的文章,里面很多基础知识希望你自己一边查阅资料一边学习。全文从业务开发中最常用见 loading 效果不同是实现讲起,说下现在前端开发在业务上应该有的思考。

入门级操作

State

最简单的实现,我们在 Loading 组件内部声明一个状态,通过代码逻辑判断 loading 效果的展示。

export default class extends Component {
  ...
  render() {
    return this.state.loading ? <div className="loader" /> : <div>finish</div>;
  }
}

完整演示

Props

随着业务的发展,这个 Loading 组件用到的地方会非常多,上面这个代码耦合了很多逻辑,为了让这个组件能够很好的复用,那我们抽离出组件的业务逻辑,将内部状态进行提升,那这个组件就是一个能被复用的 UI 组件。

export default function(props) {
  return props.loading ? <div className="loader" /> : <div>finish</div>;
}

完整演示

注:上面两段代码你可能会想,为什么 FuncClass 都能实现一个组件,他们有什么差别吗?
其实你在开发时不容易感觉到差别,但 React 本身是进行了很多差别处理,如果是 Class 类,React 会用 new 关键字实例化,然后调用该实例的 render 方法,如果是 Func 函数,React 会直接调用它。

Refs

如果你是一个 jQuery 转型 React 的开发,会很自然的想到,我找到 Loading 组件的节点,控制他的显示与隐藏,当然这也是可以的,React 提供 Refs 方便你访问 DOM 节点 或 React 元素。

export default class extends Component {
  componentDidMount() {
    fetch().then(() => {
      this.el.changeLoading(false);
    });
  }

  render() {
    return (
      <Loading ref={el => { this.el = el; }} />
    );
  }
}

完整演示

通用逻辑抽离

当你的应用做到一定的复杂度,不同的页面都会有 loading 效果,你肯定不希望每个页面都重复的书写一样的逻辑,这样会导致你的代码重复且混乱。

React 中有两个比较常见的解决方案 HOCRender Props,其实这两个这两个概念都是不依赖 React 的。

让我们暂时忘掉 React,下面我对 HOCRender Props 写两个例子,你会发现组件复用是如此简单。

HOC

HOC 其实就是一种装饰器模式,它接受一个组件作为参数,然后返回相同的组件,这样就可以额外增加一些功能。

const func = () => {
  console.log("func");
};

const wrap = func => {
  console.log("wrap");
  return func;
};

// wrap 逻辑已被复用
wrap(func)();

完整演示

Render Props

Render Props 就是我们给一个函数传递一个回调函数做为参数,该回调函数就能利用外面函数的执行结果做为参数,执行任何操作。

const func = param => {
  console.log("func");
};

const wrap = (param, func) => {
  console.log("wrap");
  func(param);
};

// wrap 逻辑已被复用
wrap("", func);

完整演示

相同点:

  • 两者都能很好的帮助我们重用组件逻辑
  • 和回调函数类似,当嵌套层数很多时,会造成回调地狱

不同点:

  • HOC 和 父组件有相同属性名属性传递过来,会造成属性丢失;
  • Render Props 你只需要实例化一个中间类,而 HOC 你每次调用的地方都需要额外实例化一个中间类。

总的来说,在需要复用组件逻辑的时候,我个人更倾向于 Render Props 的方式。

复杂状态管理

当你的应用越来越大,组件之间交互越来越复杂,那整个页面的数据逻辑将变得难以管理,这时候为了方便管理应用的状态,你可以选择一些状态管理工具,例如 ReduxFluxdva 等。

Redux

Redux

我不太想谈这些数据流框架,因为他们的概念 actionstoredispatch 太过于生涩难懂。
现代前端框架 React 和 Vue 其实都是一个套路,通过数据渲染试图,然后视图上操作反过来更新数据,重新渲染视图,刷新页面。
数据叫做 store,动作叫做 ation,触发行为叫 dispatch,然后数据到视图的渲染由 React/Vue 处理的。

(图片来自 这里

// reducers.js
const initialState = {
  loading: false
};

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case "CHANGE_LOADING":
      return {
        loading: action.payload
      };
    default:
      return state;
  }
}

完整演示

Saga

当你代码中有大量的异步操作时,例如 fetch 请求,你肯定会想到事件监听回调函数发布/订阅
很好,上一个例子其实就是事件监听的处理方式,然后回调函数的主流的解决方案是 redux-thunk,而发布/订阅的主流解决方案是 saga

import { takeLatest, put } from "redux-saga/effects";

import fetch from "./fetch";

function* fetchInfo(action) {
  yield put({
    type: "CHANGE_LOADING",
    payload: true
  });

  yield fetch();

  yield put({
    type: "CHANGE_LOADING",
    payload: false
  });
}

export default function* fetchSaga() {
  yield takeLatest("FETCH_REQUEST", fetchInfo);
}

完整演示

当你耐心看到这里,我知道你对 React 肯定有一定的经验,现在还可以做很多,例如把 loading 状态提升到 Store 的顶部,那整个站点就只有一个 loading 了,然后你还可以将 fetch 再封装一个 HOC 修改 loading 状态,这就是一个相对完美的 loading,其实 React 业务开发都可以用这个套路。

新的 API

Context

context

上面 redux 的例子是不是过于复杂
对于简单的业务,虽然有很多页面,嵌套层次也很复杂,你当然可以不用状态管理工具,你可以试着使用 Context,它可以方便你传递数据,它其实就是 Render Props 的一种实现。

export default React.createContext({
  loading: false,
  changeLoading: () => {}
});

完整演示

Hooks

写到这,静一下,是不是哪里做错了什么?

我的业务只是想写个简单的 loading 效果,却了解了一堆组件生命周期的概念。

Hooks 刚好帮你解决了这样的问题,Hooks 能允许你通过执行单个函数调用来使用函数中的 React 功能,让你把面向生命周期编程变成面向业务逻辑编程

import React, { useState, useEffect } from "react";

import Loading from "./Loading/index";
import fetch from "./fetch";

function App() {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch().then(() => {
      setLoading(false);
    });
  }, []);

  return <Loading loading={loading} />;
}

export default App;

完整演示

好好总结

上面对每个点都做了具体实现,但他们都不是隔离的,你可以根据你的认知和业务特点总结抽象一套自己的方法论;

多了解多抽象多思考,练就十八般武艺,遇到问题的时候才能游刃有余;

React 现在宣传的东西越来越多,你最好先深入了解他们,然后用批判的眼光,保持理智,防止自己每天用很新的特性重构你自己的代码。

参考文章

React 官网

When do I know I’m ready for Redux?

文章可随意转载,但请保留此 原文链接

非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。

查看原文

EXDestroyer 收藏了文章 · 2019-01-09

从 loading 的 9 种写法谈 React 业务开发

banner

前言

这是一篇比较全面讲解 React 的文章,里面很多基础知识希望你自己一边查阅资料一边学习。全文从业务开发中最常用见 loading 效果不同是实现讲起,说下现在前端开发在业务上应该有的思考。

入门级操作

State

最简单的实现,我们在 Loading 组件内部声明一个状态,通过代码逻辑判断 loading 效果的展示。

export default class extends Component {
  ...
  render() {
    return this.state.loading ? <div className="loader" /> : <div>finish</div>;
  }
}

完整演示

Props

随着业务的发展,这个 Loading 组件用到的地方会非常多,上面这个代码耦合了很多逻辑,为了让这个组件能够很好的复用,那我们抽离出组件的业务逻辑,将内部状态进行提升,那这个组件就是一个能被复用的 UI 组件。

export default function(props) {
  return props.loading ? <div className="loader" /> : <div>finish</div>;
}

完整演示

注:上面两段代码你可能会想,为什么 FuncClass 都能实现一个组件,他们有什么差别吗?
其实你在开发时不容易感觉到差别,但 React 本身是进行了很多差别处理,如果是 Class 类,React 会用 new 关键字实例化,然后调用该实例的 render 方法,如果是 Func 函数,React 会直接调用它。

Refs

如果你是一个 jQuery 转型 React 的开发,会很自然的想到,我找到 Loading 组件的节点,控制他的显示与隐藏,当然这也是可以的,React 提供 Refs 方便你访问 DOM 节点 或 React 元素。

export default class extends Component {
  componentDidMount() {
    fetch().then(() => {
      this.el.changeLoading(false);
    });
  }

  render() {
    return (
      <Loading ref={el => { this.el = el; }} />
    );
  }
}

完整演示

通用逻辑抽离

当你的应用做到一定的复杂度,不同的页面都会有 loading 效果,你肯定不希望每个页面都重复的书写一样的逻辑,这样会导致你的代码重复且混乱。

React 中有两个比较常见的解决方案 HOCRender Props,其实这两个这两个概念都是不依赖 React 的。

让我们暂时忘掉 React,下面我对 HOCRender Props 写两个例子,你会发现组件复用是如此简单。

HOC

HOC 其实就是一种装饰器模式,它接受一个组件作为参数,然后返回相同的组件,这样就可以额外增加一些功能。

const func = () => {
  console.log("func");
};

const wrap = func => {
  console.log("wrap");
  return func;
};

// wrap 逻辑已被复用
wrap(func)();

完整演示

Render Props

Render Props 就是我们给一个函数传递一个回调函数做为参数,该回调函数就能利用外面函数的执行结果做为参数,执行任何操作。

const func = param => {
  console.log("func");
};

const wrap = (param, func) => {
  console.log("wrap");
  func(param);
};

// wrap 逻辑已被复用
wrap("", func);

完整演示

相同点:

  • 两者都能很好的帮助我们重用组件逻辑
  • 和回调函数类似,当嵌套层数很多时,会造成回调地狱

不同点:

  • HOC 和 父组件有相同属性名属性传递过来,会造成属性丢失;
  • Render Props 你只需要实例化一个中间类,而 HOC 你每次调用的地方都需要额外实例化一个中间类。

总的来说,在需要复用组件逻辑的时候,我个人更倾向于 Render Props 的方式。

复杂状态管理

当你的应用越来越大,组件之间交互越来越复杂,那整个页面的数据逻辑将变得难以管理,这时候为了方便管理应用的状态,你可以选择一些状态管理工具,例如 ReduxFluxdva 等。

Redux

Redux

我不太想谈这些数据流框架,因为他们的概念 actionstoredispatch 太过于生涩难懂。
现代前端框架 React 和 Vue 其实都是一个套路,通过数据渲染试图,然后视图上操作反过来更新数据,重新渲染视图,刷新页面。
数据叫做 store,动作叫做 ation,触发行为叫 dispatch,然后数据到视图的渲染由 React/Vue 处理的。

(图片来自 这里

// reducers.js
const initialState = {
  loading: false
};

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case "CHANGE_LOADING":
      return {
        loading: action.payload
      };
    default:
      return state;
  }
}

完整演示

Saga

当你代码中有大量的异步操作时,例如 fetch 请求,你肯定会想到事件监听回调函数发布/订阅
很好,上一个例子其实就是事件监听的处理方式,然后回调函数的主流的解决方案是 redux-thunk,而发布/订阅的主流解决方案是 saga

import { takeLatest, put } from "redux-saga/effects";

import fetch from "./fetch";

function* fetchInfo(action) {
  yield put({
    type: "CHANGE_LOADING",
    payload: true
  });

  yield fetch();

  yield put({
    type: "CHANGE_LOADING",
    payload: false
  });
}

export default function* fetchSaga() {
  yield takeLatest("FETCH_REQUEST", fetchInfo);
}

完整演示

当你耐心看到这里,我知道你对 React 肯定有一定的经验,现在还可以做很多,例如把 loading 状态提升到 Store 的顶部,那整个站点就只有一个 loading 了,然后你还可以将 fetch 再封装一个 HOC 修改 loading 状态,这就是一个相对完美的 loading,其实 React 业务开发都可以用这个套路。

新的 API

Context

context

上面 redux 的例子是不是过于复杂
对于简单的业务,虽然有很多页面,嵌套层次也很复杂,你当然可以不用状态管理工具,你可以试着使用 Context,它可以方便你传递数据,它其实就是 Render Props 的一种实现。

export default React.createContext({
  loading: false,
  changeLoading: () => {}
});

完整演示

Hooks

写到这,静一下,是不是哪里做错了什么?

我的业务只是想写个简单的 loading 效果,却了解了一堆组件生命周期的概念。

Hooks 刚好帮你解决了这样的问题,Hooks 能允许你通过执行单个函数调用来使用函数中的 React 功能,让你把面向生命周期编程变成面向业务逻辑编程

import React, { useState, useEffect } from "react";

import Loading from "./Loading/index";
import fetch from "./fetch";

function App() {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch().then(() => {
      setLoading(false);
    });
  }, []);

  return <Loading loading={loading} />;
}

export default App;

完整演示

好好总结

上面对每个点都做了具体实现,但他们都不是隔离的,你可以根据你的认知和业务特点总结抽象一套自己的方法论;

多了解多抽象多思考,练就十八般武艺,遇到问题的时候才能游刃有余;

React 现在宣传的东西越来越多,你最好先深入了解他们,然后用批判的眼光,保持理智,防止自己每天用很新的特性重构你自己的代码。

参考文章

React 官网

When do I know I’m ready for Redux?

文章可随意转载,但请保留此 原文链接

非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj(at)alibaba-inc.com 。

查看原文

EXDestroyer 提出了问题 · 2017-11-16

echarts如何为节点添加自定义动画?

我想给每个节点添加一个移动的动画,能否实现

关注 2 回答 0

EXDestroyer 关注了用户 · 2017-11-07

Fundebug @fundebug

一行代码搞定BUG监控:https://www.fundebug.com/

关注 1324

EXDestroyer 赞了回答 · 2017-09-11

解决defer和async的区别

先来试个一句话解释仨,当浏览器碰到 script 脚本的时候:

  1. <script data-original="script.js"></script>

    没有 deferasync,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

  2. <script async data-original="script.js"></script>

    async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

  3. <script defer data-original="myscript.js"></script>

    defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

然后从实用角度来说呢,首先把所有脚本都丢到 </body> 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。

接着,我们来看一张图咯:

请输入图片描述

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

此图告诉我们以下几个要点:

  1. deferasync 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的
  3. 关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
  5. 仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

关注 69 回答 6

EXDestroyer 回答了问题 · 2017-08-23

js中for循环里面有异步请求怎么解决?

可以用生成器函数或者async/await再用babel转

关注 7 回答 5

EXDestroyer 回答了问题 · 2017-08-22

解决js数组中英文按首字母排序

楼上那种带标识的方法比较好,如果你非要中英文混在一起比较那你得现有一个获取汉字对应拼音的库,并且你要考虑多音字问题

关注 6 回答 5

认证与成就

  • 获得 26 次点赞
  • 获得 120 枚徽章 获得 5 枚金徽章, 获得 41 枚银徽章, 获得 74 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-05-15
个人主页被 869 人浏览