面条

面条 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

面条 关注了专栏 · 11月2日

前端进阶!

虽然说是前端,但是总觉得做着做着就搞到后端了

关注 459

面条 收藏了文章 · 6月5日

html2canvas以及domtoimage的使用踩坑总结

前言

首先做个自我介绍,我是成都某企业的一名刚刚入行约一年的前端,在之前的开发过程中,遇到了问题,也解决了问题,但是在下一次解决相同问题的时候,只对这个问题有一丝丝的印象,还需要从新去查找,于是,我注册了segmemtfault,便于搜集我的问题总结,以及将踩坑经验分享给每一个开发人员,好了,闲话不多说。

需求

需求:要求能够实现根据后端返回的数据生成一张image,便于用户将图片分享到朋友或者朋友圈,取得用户的关注。

开始踩坑

一.html2canvas对于跨域图片,转换的时候会将跨域图片识别为空白。
问题分析:
既然是由于跨域引起的问题,那我们让资源不跨域不就可以访问了吗?
解决办法:
将图片放置服务器,通过nginx进行代理资源,前端访问图片便不涉及到跨域问题。

二.html2canvas动态加载内容,通过canvas转换出来的数据,图片为空
问题分析:
内容是动态加载进来的,转换肯定是在请求完毕之后再去转换,但是在请求完毕之后去转换,按理说所需要的所有数据都已经到达前端,应该可以转换,经过思考,发现图片内容从后台读取需要一定时间去解析,才能够完整的将图片资源展示出来,html2canvas是将页面上显示的dom元素,经过解析将dom画在canvas上在转换为image图片格式。
解决办法:
1.让html2canvas转换代码等待一定时间,在进行转换操作,可进行转换。代码如下图所示

clipboard.png

2.当全部的图片数据都加载完毕之后,在执行转换操作。(本人建议第二种,更保险)

clipboard.png

三.html2canvas转换的base64位图不能被ios8以上版本所识别。会呈现出整个截图页面空白
问题分析:
这个问题的起因,应该是html2canvas对高版本的ios不支持(自我感觉),这个问题我很是头疼,当时根本没有对ios进行测试。客户使用的时候发现了这个问题,没法。想办法解决。百度说是由于ios不能识别base64的前缀,于是我试过将图片的前缀去除,但发现没反应。还是无用。思来想去感觉html2canvas坑太多了。填都填不完。于是。
解决办法:
我采用了另一款插件,dom-to-image,弄上去没有问题了。

clipboard.png

四.dom-to-image运用上去,在ios上能够出现内容了,但发现存在一个问题,部分图片内容,第一次进行公众号网页加载,没有正确显示,要在次进入才会显示,此bug同样是ios8以上版本
问题分析:
这一个问题我没有找到问题所在,一脸懵,不过最终还是得到了解决。
解决办法:
运用dom-to-imagede toSvg方式完美解决问题。

clipboard.png

五.离成功只有一步之遥了,使用了svg之后安卓手机不能将图片分享给朋友。识别不了
问题分析:
安卓能识别jpeg但不能识别svg矢量图片
解决办法:
自己手写咯。判断手机为安卓还是ios。

clipboard.png

六.所有的问题都已解决,我兴奋的跑去借了个果5,拿来测试,发现网页的背景图片不见了。
问题分析:
我长按两秒左右又是正常显示了,故图片是已经完美转换成功,可能是因为图片在转换过程中,没有完美适配到4.0寸的屏幕,
解决办法:
模拟长按事件,解决bug,这个方式可能不太好,但也是一种解决方式。

总结

躺过的坑都是我的经验,分享的同时我又加固了一遍。解决实际问题的思路,在过了一遍,对我帮助很棒。

查看原文

面条 回答了问题 · 2019-07-25

解决RN如何实现 View + FlatList 同时存在的情况下,既可以滑动List,又可以滑动整个页面?

ScrollView里嵌套FlatList会导致,FlatList里的滚动事件不被触发刚开始的时候疯狂触发onEndReached,滑动到底部的时候又不触发onEndReached。这个你怎么解决的

关注 4 回答 3

面条 关注了用户 · 2019-05-15

alisecued @alisecued

关注 522

面条 关注了用户 · 2019-04-18

trigkit4 @trigkit4

新博客:http://github.com/hawx1993/te...

欢迎吐槽和关注~

关注 3531

面条 关注了用户 · 2019-04-01

阿里云云栖号 @yunqishequ_5aa899aad5395

阿里云官网内容平台!汇聚阿里云优质内容(入门、文档、案例、最佳实践、直播等)!如需转载或内容类合作,邮件yqgroup@service.aliyun.com 秒级回复!

关注 10578

面条 收藏了文章 · 2018-11-01

【React源码解读】- 组件的实现

前言

react使用也有一段时间了,大家对这个框架褒奖有加,但是它究竟好在哪里呢?
让我们结合它的源码,探究一二!(当前源码为react16,读者要对react有一定的了解)

15397566862932

回到最初

根据react官网上的例子,快速构建react项目

npx create-react-app my-app

cd my-app

npm start

打开项目并跑起来以后,暂不关心项目结构及语法糖,看到App.js里,这是一个基本的react组件<App/> 我们console一下,看看有什么结果。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img data-original={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
        </header>
      </div>
    );
  }
}

export default App;

console.log(<App/>)

15397572879758

可以看到,<App/>组件其实是一个JS对象,并不是一个真实的dom。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。有兴趣的同学可以去阮一峰老师的ES6入门详细了解一下

上面有我们很熟悉的props,ref,key,我们稍微修改一下console,看看有什么变化。

console.log(<App key={1} abc={2}><div>你好,这里是App组件</div></App>)

15397577334580

可以看到,props,key都发生了变化,值就是我们赋予的值,props中嵌套了children属性。可是为什么我们嵌入的是div,实际上却是一个对象呢?

打开源码

/node_modules/react

15397580720896

首先打开index.js

'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

可以知道目前用上的是./cjs/react.development.js,直接打开文件。
根据最初的代码,我们组件<App/>用到了React.Component。找到React暴露的接口:

15397617558881

接着找到Component: Component方法,

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

上面就是一些简单的构造函数,也可以看到,我们常用的setState是定义在原型上的2个方法。

至此,一个<App/>组件已经有一个大概的雏形:

15397595217487

到此为止了吗?这看了等于没看啊,究竟组件是怎么变成div的?render吗?
可是全局搜索,也没有一个function是render啊。

原来,我们的jsx语法会被babel编译的。

15397600724075

这下清楚了,还用到了React.createElement

createElement: createElementWithValidation,

通过createElementWithValidation,

function createElementWithValidation(type, props, children) {
······

  var element = createElement.apply(this, arguments);


  return element;
}

可以看到,return了一个element,这个element又是继承自createElement,接着往下找:

function createElement(type, config, children) {
  var propName = void 0;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;
······
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}

这里又返回了一个ReactElement方法,再顺着往下找:

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner
  };

······
  return element;
};

诶,这里好像返回的就是element对象,再看我们最初的<App/>的结构,是不是很像

15397606651880验证一下我们的探索究竟对不对,再每一个方法上我们都打上console,(注意,将App里的子元素全部删空,利于我们观察)

15397611759810

React.createElement 、 createElementWithValidation 、 createElement 、 ReactElement,通过这些方法,我们用class声明的React组件在变成真实dom之前都是ReactElement类型的js对象

createElementWithValidation:

  • 首先校验type是否是合法的

15397657382603

  • 校验了props是否符合设置的proptypes

15397667118968

  • 校验了子节点的key,确保每个数组中的元素都有唯一的key

15397667422295

createElement

  • type是你要创建的元素的类型,可以是html的div或者span,也可以是其他的react组件,注意大小写
  • config中包含了props、key、ref、self、source等

15397667913454

  • 向props加入children,如果是一个就放一个对象,如果是多个就放入一个数组。

15397668352993

  • 那如果type.defaultProps有默认的props时,并且对应的props里面的值是undefined,把默认值赋值到props中

15397668766904

  • 也会对key和ref进行校验

15397669476655

ReactElement

ReactElement就比较简单了,创建一个element对象,参数里的type、key、ref、props、等放进去,然后return了。最后调用Object.freeze使对象不可再改变。

组件的挂载

我们上面只是简单的探究了<App/>的结构和原理,那它究竟是怎么变成真实dom的呢

15397616989193

ReactDOM.render(<App />, document.getElementById('root'));

我们接着用babel编译一下:

15397619877496

原来ReactDOM.render调用的是render方法,一样,找暴露出来的接口。


var ReactDOM = {
······
  render: function (element, container, callback) {
    return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
  },
······
};

它返回的是一个legacyRenderSubtreeIntoContainer方法,这次我们直接打上console.log

15397629379495

这是打印出来的结果,

15397633591876

legacyRenderSubtreeIntoContainer
这个方法除主要做了两件事:

  • 清除dom容器元素的子元素
while (rootSibling = container.lastChild) {
      {
        if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
          warned = true;
        }
      }
      container.removeChild(rootSibling);
    }
  • 创建ReactRoot对象

15397648731115

源码暂时只读到了这里,关于React16.1~3的新功能,以及新的生命周期的使用和原理、Fiber究竟是什么,我们将在后续文章接着介绍。

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

查看原文

面条 评论了文章 · 2018-10-19

webpack v3 结合 react-router v4 做 dynamic import — 按需加载(懒加载)

为什么要做dynamic import?

dynamic import不知道为什么有很多叫法,什么按需加载,懒加载,Code Splitting,代码分页等。
总之,就是在SPA,把JS代码分成N个页面份数的文件,不在用户刚进来就全部引入,而是等用户跳转路由的时候,再加载对应的JS文件。
这样做的好处就是加速首屏显示速度,同时也减少了资源的浪费。

为什么选择 webpack 3?

  • 更高的性能
  • 有scope hosting功能,不再需要rollup来处理代码冗余
  • 可与react-router结合,更优雅的做dynamic import
  • 最重要的一点是,我正经学webpack的时候3已结出了- -

完整的 react spa 项目地址

GitHub项目地址

这个一个完整的项目,这节相关的内容可在router/routerMap.jsx中找到。

第一步:安装 babel-plugin-syntax-dynamic-import

babel用的是babel-env,使用方法可以去babel官方学习,实践可看我项目的源代码。

npm i -D babel-plugin-syntax-dynamic-import 以后, 在.babelrc文件的plungins中加上"syntax-dynamic-import"

第二步:安装 react-loadable

npm i -S react-loadable 以后,我们就能愉快得做dynamic import了。

第三步: 编辑routerMap

import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();

import App from 'containers';

// 按路由拆分代码
import Loadable from 'react-loadable';
const MyLoadingComponent = ({ isLoading, error }) => {
    // Handle the loading state
    if (isLoading) {
        return <div>Loading...</div>;
    }
    // Handle the error state
    else if (error) {
        return <div>Sorry, there was a problem loading the page.</div>;
    }
    else {
        return null;
    }
};
const AsyncHome = Loadable({
    loader: () => import('../containers/Home'),
    loading: MyLoadingComponent
});
const AsyncCity = Loadable({
    loader: () => import('../containers/City'),
    loading: MyLoadingComponent
});
const AsyncDetail = Loadable({
    loader: () => import('../containers/Detail'),
    loading: MyLoadingComponent
});
const AsyncSearch = Loadable({
    loader: () => import('../containers/Search'),
    loading: MyLoadingComponent
});
const AsyncUser = Loadable({
    loader: () => import('../containers/User'),
    loading: MyLoadingComponent
});
const AsyncNotFound = Loadable({
    loader: () => import('../containers/404'),
    loading: MyLoadingComponent
});

// 路由配置
class RouteMap extends React.Component {
    render() {
        return (
            <Router history={history}>
                <App>
                    <Switch>
                        <Route path="/" exact component={AsyncHome} />
                        <Route path="/city" component={AsyncCity} />
                        <Route path="/search/:category/:keywords?" component={AsyncSearch} />
                        <Route path="/detail/:id" component={AsyncDetail} />
                        <Route path="/user" component={AsyncUser} />
                        <Route path="/empty" component={null} key="empty" />
                        <Route component={AsyncNotFound} />
                    </Switch>
                </App>
            </Router>
        );
        // 说明
        // empty Route
        // https://github.com/ReactTraining/react-router/issues/1982  解决人:PFight
        // 解决react-router v4改变查询参数并不会刷新或者说重载组件的问题 
    }
}

export default RouteMap;

大功告成

我们可以运行webpack,然后就能看到效果(图仅为dev环境,build才会再打包一个vendor.js,为什么要有vendor.js,请见devDependencies和dependencies的区别 >>

clipboard.png

参考文章

Code Splitting in Create React App

Q&A

有同学表示,我的方法做页面分离并没有什么好处,因为每个页面都依赖了三方库的代码,所以其实页面有很多冗余代码,能想到这点很棒,已经开始有架构思维了。不过,注意这个想法在dev环境下,这个同学是对的。

那到了build环境,或者说到了发布环境,又是怎么样的呢?的确,这篇文章我没有提到,请见我的另一篇文章devDependencies和dependencies的区别。这篇文章主要解释了npm的package.json中devDependencies和dependencies区别是什么。

看完以后,我们就可以知道,为什么我之前说“注意这个想法在dev环境下,这个同学是对的”了。因为,我们npm run build以后,webpack会把三方包打包到vendor.js文件,页面逻辑代码不会牵涉其中,每个页面都会引用vendor.js这个文件,这样的话,就不会出现重复引入冗余代码的情况了。

查看原文

面条 评论了文章 · 2018-10-18

webpack v3 结合 react-router v4 做 dynamic import — 按需加载(懒加载)

为什么要做dynamic import?

dynamic import不知道为什么有很多叫法,什么按需加载,懒加载,Code Splitting,代码分页等。
总之,就是在SPA,把JS代码分成N个页面份数的文件,不在用户刚进来就全部引入,而是等用户跳转路由的时候,再加载对应的JS文件。
这样做的好处就是加速首屏显示速度,同时也减少了资源的浪费。

为什么选择 webpack 3?

  • 更高的性能
  • 有scope hosting功能,不再需要rollup来处理代码冗余
  • 可与react-router结合,更优雅的做dynamic import
  • 最重要的一点是,我正经学webpack的时候3已结出了- -

完整的 react spa 项目地址

GitHub项目地址

这个一个完整的项目,这节相关的内容可在router/routerMap.jsx中找到。

第一步:安装 babel-plugin-syntax-dynamic-import

babel用的是babel-env,使用方法可以去babel官方学习,实践可看我项目的源代码。

npm i -D babel-plugin-syntax-dynamic-import 以后, 在.babelrc文件的plungins中加上"syntax-dynamic-import"

第二步:安装 react-loadable

npm i -S react-loadable 以后,我们就能愉快得做dynamic import了。

第三步: 编辑routerMap

import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();

import App from 'containers';

// 按路由拆分代码
import Loadable from 'react-loadable';
const MyLoadingComponent = ({ isLoading, error }) => {
    // Handle the loading state
    if (isLoading) {
        return <div>Loading...</div>;
    }
    // Handle the error state
    else if (error) {
        return <div>Sorry, there was a problem loading the page.</div>;
    }
    else {
        return null;
    }
};
const AsyncHome = Loadable({
    loader: () => import('../containers/Home'),
    loading: MyLoadingComponent
});
const AsyncCity = Loadable({
    loader: () => import('../containers/City'),
    loading: MyLoadingComponent
});
const AsyncDetail = Loadable({
    loader: () => import('../containers/Detail'),
    loading: MyLoadingComponent
});
const AsyncSearch = Loadable({
    loader: () => import('../containers/Search'),
    loading: MyLoadingComponent
});
const AsyncUser = Loadable({
    loader: () => import('../containers/User'),
    loading: MyLoadingComponent
});
const AsyncNotFound = Loadable({
    loader: () => import('../containers/404'),
    loading: MyLoadingComponent
});

// 路由配置
class RouteMap extends React.Component {
    render() {
        return (
            <Router history={history}>
                <App>
                    <Switch>
                        <Route path="/" exact component={AsyncHome} />
                        <Route path="/city" component={AsyncCity} />
                        <Route path="/search/:category/:keywords?" component={AsyncSearch} />
                        <Route path="/detail/:id" component={AsyncDetail} />
                        <Route path="/user" component={AsyncUser} />
                        <Route path="/empty" component={null} key="empty" />
                        <Route component={AsyncNotFound} />
                    </Switch>
                </App>
            </Router>
        );
        // 说明
        // empty Route
        // https://github.com/ReactTraining/react-router/issues/1982  解决人:PFight
        // 解决react-router v4改变查询参数并不会刷新或者说重载组件的问题 
    }
}

export default RouteMap;

大功告成

我们可以运行webpack,然后就能看到效果(图仅为dev环境,build才会再打包一个vendor.js,为什么要有vendor.js,请见devDependencies和dependencies的区别 >>

clipboard.png

参考文章

Code Splitting in Create React App

Q&A

有同学表示,我的方法做页面分离并没有什么好处,因为每个页面都依赖了三方库的代码,所以其实页面有很多冗余代码,能想到这点很棒,已经开始有架构思维了。不过,注意这个想法在dev环境下,这个同学是对的。

那到了build环境,或者说到了发布环境,又是怎么样的呢?的确,这篇文章我没有提到,请见我的另一篇文章devDependencies和dependencies的区别。这篇文章主要解释了npm的package.json中devDependencies和dependencies区别是什么。

看完以后,我们就可以知道,为什么我之前说“注意这个想法在dev环境下,这个同学是对的”了。因为,我们npm run build以后,webpack会把三方包打包到vendor.js文件,页面逻辑代码不会牵涉其中,每个页面都会引用vendor.js这个文件,这样的话,就不会出现重复引入冗余代码的情况了。

查看原文

面条 关注了专栏 · 2018-10-18

美团技术团队

“美团技术团队”,你最值得关注的技术团队官方微信公众号。 每周会推送来自一线的实践技术文章,涵盖前端(Web、iOS和Android)、后台、大数据、AI/算法、测试、运维等技术领域。 在这里,与8000多业界一流工程师交流切磋。

关注 10149

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-09-03
个人主页被 170 人浏览