Code Monkey home page Code Monkey logo

blog's Introduction

Hi there 👋

blog's People

Contributors

dependabot[bot] avatar hancoson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

React compontent 分解技能【翻译】

React 组件非常的强大和灵活,提供了强大的功能,但是随着时间的推移,它将变的越来越臃肿和繁琐。

与任何其他类型的编程相比,React 组件遵循单一功能原则( single responsibility principle),它不仅使你的组件更容易维护,更重要的是复用性。然而,如何负责一个庞大的React组件不再是一件容易的事。下面从易到难我买将有有三种方法具体讲解这个问题。

1. 分离 render()方法

这个是最常见的方法:当一个组件要呈现太多的元素,把这些元素分解为逻辑子组件是一个简单的简化方法。

一个常见而快速的方法就是在同一类中拆分render()方法并创建额外的“sub-render”方法:

class Panel extends React.Component {
  renderHeading() {
    // ...
  }

  renderBody() {
    // ...
  }

  render() {
    return (
      <div>
        {this.renderHeading()}
        {this.renderBody()}
      </div>
    );
  }
}

虽然这中方法有它的用处,但这不是一个真正的拆分组件本身的方法。每个statepropsclass methods等仍然是共享的,所以很难确定哪些是由每个sub-render所使用。

要真正降低复杂性,应该创建全新的组件。对于更简单的子部件,功能组件可用于将样板保持在最低限度:

const PanelHeader = (props) => (
  // ...
);

const PanelBody = (props) => (
  // ...
);

class Panel extends React.Component {
  render() {
    return (
      <div>
        // Nice and explicit about which props are used
        <PanelHeader title={this.props.title}/>
        <PanelBody content={this.props.content}/>
      </div>
    );
  }
}

通过这种方式拆分还有一个微妙但重要的区别。代替直接与间接函数调用组件声明,我们以更小的单位来组织React代码。这是因为返回的‘Panel’的render()是一个树形的元素,只有走到PanelHeaderPanelBody,而不是在它们之下的所有元素。

来看个实际意义的测试:一个浅渲染可以用来轻松隔离这些单位进行独立的测试。作为奖励,当React的新语境体系到达时,规模较小的单位将使它更有效地执行增量呈现。

模版组件通过React元素作为props

当一个组件由于多个变量或者配置变得非常的复杂,考虑将这个组件转化成一个“模版”组件。这就需要一个专用的父组件集中配置它们。

例如,一个Comment组件可能有不同的actions或者 metadata显示,取决于你是不是作者,是否保存成功,或者有无权限等。而不是混合的Comment组件,在哪里以及如何呈现它的内容?用逻辑来处理所有的变化?这两个问题可以被认为是独立的。使用React通过元素的能力,不仅仅用数据作为props,来创建一个灵活的模板组件。

class CommentTemplate extends React.Component {
  static propTypes = {
    // Declare slots as type node
    metadata: PropTypes.node,
    actions: PropTypes.node,
  };
  
  render() {
    return (
      <div>
        <CommentHeading>
          <Avatar user={...}/>
          
          // Slot for metadata
          <span>{this.props.metadata}</span>
          
        </CommentHeading>
        <CommentBody/>
        <CommentFooter>
          <Timestamp time={...}/>
          
          // Slot for actions
          <span>{this.props.actions}</span>
          
        </CommentFooter>
      </div>
    );
  }
}

另一个组件可以找出的独有的metadataactions

class Comment extends React.Component {
  render() {
    const metadata = this.props.publishTime ?
      <PublishTime time={this.props.publishTime} /> :
      <span>Saving...</span>;
    
    const actions = [];
    if (this.props.isSignedIn) {
      actions.push(<LikeAction />);
      actions.push(<ReplyAction />);
    }
    if (this.props.isAuthor) {
      actions.push(<DeleteAction />);
    }
    
    return <CommentTemplate metadata={metadata} actions={actions} />;
  }
}

请记住,在JSX中,组件的开始和结束标签之间的任何东西都作为特殊的子类支持传递。当正确使用时,可以特别表现出来。为了使用习惯,应该作为组件的主要内容区域保留。在评论示例中,这可能是注释本身的文本。

<CommentTemplate metadata={metadata} actions={actions}>
  {text}
</CommentTemplate>

将常见方面提取到高阶组件中

组件常常会被与其主要并且直接相关的交叉问题所污染。

假设你只要点击Document组件中的一个链接来发送分析数据。为了使事情进一步复杂化,数据需要包括有关文档的信息,如ID。简单的解决方案可能是向DocumentcomponentDidMountcomponentWillUnmount生命周期方法添加代码,如下所示:

class Document extends React.Component {
  componentDidMount() {
    ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
  }
  
  componentWillUnmount() {
    ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
  }
  
  onClick = (e) => {
    if (e.target.tagName === 'A') { // Naive check for <a> elements
      sendAnalytics('link clicked', {
        documentId: this.props.documentId // Specific information to be sent
      });
    }
  };
  
  render() {
    // ...
  }
}

这里有一些问题:

  • 该组件现在有一个额外的关注,掩盖其主要目的:显示文档。
  • 如果该组件在这些生命周期方法中具有其他逻辑,则分析代码也会变得模糊。
  • 分析代码不可重用。
  • 重新构建组件变得更加困难,因为您必须解决分析代码。

可以使用高阶分量(HOCs)来分解这样的问题。简而言之,这些函数可以应用于任何React组件,用所需的行为来包装该组件。

function withLinkAnalytics(mapPropsToData, WrappedComponent) {
  class LinkAnalyticsWrapper extends React.Component {
    componentDidMount() {
      ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
    }

    componentWillUnmount() {
      ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
    }

    onClick = (e) => {
      if (e.target.tagName === 'A') { // Naive check for <a> elements
        const data = mapPropsToData ? mapPropsToData(this.props) : {};
        sendAnalytics('link clicked', data);
      }
    };
    
    render() {
      // Simply render the WrappedComponent with all props
      return <WrappedComponent {...this.props} />;
    }
  }
  
  return LinkAnalyticsWrapper;
}

重要的是要注意,该函数不会使组件变异以添加其行为,而是返回一个新的包装组件。正是这个新的包装组件用于代替原始的Document组件。

class Document extends React.Component {
  render() {
    // ...
  }
}

export default withLinkAnalytics((props) => ({
  documentId: props.documentId
}), Document);

请注意,可以提取要发送的数据(documentId)的具体细节作为HOCs的配置。这可以保留文档的领域知识与文档组件,以及通用的方式来监听withLinkAnalytics HOCs中的点击。

高阶组件显示了React的强大的组合性质。这里的简单示例演示了如何将似乎紧密集成的代码提取到具有单一职责的模块中。

HOCs通常用于React库,例如react-reduxstyled-componentsreact-intl。毕竟,这些库都是解决任何React应用程序的通用方面。另一个库——recompose,通过使用HOCs进一步管理从组件状态到生命周期方法的一切。

结论

React组件是高度可组合的设计。通过轻松分解和组合它们,可以利用这一点。

不要躲避创建小而重要的组件。起初它可能会让你感到尴尬,但结果将是更强大和可重用的代码。


原文:https://medium.com/dailyjs/techniques-for-decomposing-react-components-e8a1081ef5da

Fultter开发 —— Dart语法

变量

var name = 'Bob';

Default value(默认值)

没有初始化的变量自动获取一个默认值为 null。

int lineCount;
assert(lineCount == null);
// Variables (even if they will be numbers) are initially null.

Optional types(可选的类型)

在声明变量的时候,你可以选择加上具体 类型:

String name = 'Bob';

添加类型可以更加清晰的表达你的意图。 IDE 编译器等工具有可以使用类型来更好的帮助你, 可以提供代码补全、提前发现 bug 等功能。

Final and const

如果你以后不打算修改一个变量,使用 final 或者 const。 一个 final 变量只能赋值一次;一个 const 变量是编译时常量。 (Const 变量同时也是 final 变量。) 顶级的 final 变量或者类中的 final 变量在 第一次使用的时候初始化。

Built-in types(内置的类型)

Dart 内置支持下面这些类型:

  • numbers
    • int
    • double
  • strings
  • booleans
  • lists(array)
  • maps
  • runs(用于在字符串中表示 Unicode 字符)
  • symbols

使用三个单引号或者双引号也可以 创建多行字符串对象

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

Dart 有一个名字为 bool 的类型。 只有两个对象是布尔类型的:true 和 false 所创建的对象, 这两个对象也都是编译时常量。

赋值操作符

??= 操作符用来指定 值为 null 的变量的值。

b ??= value; // 如果 b 是 null,则赋值给 b;
             // 如果不是 null,则 b 的值保持不变

条件表达式

expr1 ?? expr2
如果 expr1non-null,返回其值; 否则执行 expr2 并返回其结果。

级联操作符 ..

级联操作符 (..) 可以在同一个对象上 连续调用多个函数以及访问成员变量。 使用级联操作符可以避免创建 临时变量, 并且写出来的代码看起来 更加流畅:

例如下面的代码:

querySelector('#button') // Get an object.
  ..text = 'Confirm'   // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

第一个方法 querySelector()`` 返回了一个 selector` 对象。 后面的级联操作符都是调用这个对象的成员, 并忽略每个操作 所返回的值。

上面的代码和下面的代码功能一样:

var button = querySelector('#button');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

断言(Assert)

如果条件表达式结果不满足需要,则可以使用 assert 语句俩打断代码的执行。 下面介绍如何使用断言。 下面是一些示例代码:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

注意: 断言只在检查模式下运行有效,如果在生产模式 运行,则断言不会执行。

使用 import 来指定一个库如何使用另外 一个库。import 必须参数为库的 URI。 对于内置的库,URI 使用特殊的 dart: scheme。 对于其他的库,你可以使用文件系统路径或者 package: schemepackage: scheme 指定的库通过包管理器来提供, 例如 pub 工具。

import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

Lazily loading a library(延迟载入库)

Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再 加载库。 下面是一些使用延迟加载库的场景:

  • 减少 APP 的启动时间。
  • 执行 A/B 测试,例如 尝试各种算法的 不同实现。
  • 加载很少使用的功能,例如可选的屏幕和对话框。
  • 要延迟加载一个库,需要先使用 deferred as 来 导入:
import 'package:deferred/hello.dart' deferred as hello;

当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库:

greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

元数据(Metadata)

使用元数据给代码添加更多的信息。
元数据是以@开始的修饰符。在`@`` 后面接着编译时的常量或调用一个常量构造函数。
目前Dart语言提供的三个可用的@修饰符:

工厂构造函数

  • 工厂构造函数是一种构造函数,与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象。
  • 工厂构造函数的关键字为factory,下面我们用工厂构造函数写一个只能创造一个实例的类。
  • 工厂构造函数类似于static静态成员,无法访问this指针,所以在工厂构造函数中能处理的任务较为有限。
  • 使用工厂构造函数时往往需要定义一个命名构造函数用来生产实例。
    例子:
class Model {
  final String id;
  final String name;

  Recommend({this.id, this.name});

  factory Model.fromJson(Map<String, dynamic> json) {
    return Model(
      id: json['id'],
      name: json['name']
    );
  }
}

3.8 工厂构造函数 – 《简单易懂的Dart》 - BlackGlory

Webp 在项目中的尝试

A new image format for the Web. ——Google

想必对 Webp 格式的图片已经有过很多的了解,都知道它可以降低图片的体积,但兼容性不好等等。今天,我就从具体的项目中来说说 Webp 。

使用背景

外界因素

并非所有浏览器都支持 WebP,但是还要使用,所以很重要的是你得清楚哪些浏览器是支持的,这可能会影响你做决定,是否在你的网站或项目中采用 WebP 图片。下面是所有主流浏览器及其 WebP支持程度(截止 2018 年 12月)。

浏览器兼容

从图片可以看出,市面上的浏览器已经达到 72.85% 的支持率,这已经是个不低的数据了。

WebP 支持度在 70% 左右浮动,使用这种图片格式来替代 PNG 和 JPEG 是有意义的。还有重要的一点要提到,无论如何实现 WebP,我们只是为支持的浏览器提供 WebP 服务,而为其他浏览器提供 PNG 或 JPEG。当然,到这里还有另外一些东西需要考虑,如浏览器市场份额,WebP 相对于 PNG 和 JPEG 的文件体积大小等。

浏览器市场份额

Clicky 截至 2017 年 12 月的数据显示,Chrome 占有市场份额为 59% 的,Firfox 以大约 14% 排名第二。

浏览器份额

从上面的数据可以看到,Chrome 占有约 59% 的市场份额,所以必须意识到,如果在项目中加入 Webp 图片,大多数访客都会看到 WebP 版本,其他用户看到其他图片的版本。

WebP 文件体积

已经有大量研究比较了 WebP 与 PNG、JPEG 的文件体积。下面来看看其中一条:

好了,我还是再举一个实际的例子看看 PNG 和 Webp 格式图片的差距:

png-webp

尽管只有一张图片,也是可以看出可观的差距来。

自身因素

众所周知,我们的活动页面整体大小的 50% 以上都被图片占用了,所以对图片做优化就迫在眉睫了。

png-webp

如何使用

把 Webp 说的这好,那么接下来就要说说怎么使用了,那就直接上代码了:

// isSupportWebp true--支持||false--不支持
window.isSupportWebp = !![].map && document
    .createElement('canvas')
    .toDataURL('image/webp')
    .indexOf('data:image/webp') == 0;

!![].map 主要是判断是否是IE9+,以免 toDataURL 方法会挂掉。

效果

那么接下来就是说说使用后的效果(数据)了。

那么接下来就是说说使用后的效果(数据)了。

优化前:

优化后:

以上两个活动是复制的双十一的移动端会场。在相同的网络环境下并且在首屏都渲染完成的情况如下:

对比项 优化前 优化后
请求数 61个 20个
首屏加载页面大小 3.1M 484KB
加载完成时间 3.49秒 1.69秒

从数据对比表可以看出性能提升在 50% 以上。当让优化还是要持久做下去,相信以后会更加的好。

参考资料:

React中的演示(UI)组件和容器组件

React中的组件可以分为presentational components即UI(演示)组件和container components即容器组件。今天就来说说他们两个的区别以及这样的好处。

区别

我的演示组件:

  • 它只关心事物的样子。
  • 内部可以包含容器组件,通常有一些自己的DOM标记和样式。
  • 通常可以通过this.props.children进行控制。
  • 对应用程序的其余部分没有依赖关系,例如Fluxactionstores
  • 不需要指定数据的加载或变异。
  • 通过props接受和会掉数据。
  • 很少有自己的状态。
  • 被编写为功能组件,除非它们需要状态,生命周期或性能优化。
  • 示例:页面,边栏,故事,用户信息,列表。

我的容器组件:

  • 关心事情如何运作
  • 可能包含容器组件,但通常没有自己的任何DOM标记,除了一些包装div,并且没有任何样式。
  • 向演示组件或其他容器组件提供数据和行为。
  • 调用Flux/redux操作,并将其作为回调给演示组件。
  • 通常是有状态的,因为它们往往作为数据源。
  • 通常使用更高阶的组件(如来自Reat Reduxconnect()),来自RelaycreateContainer()或来自Flux UtilsContainer.create()生成的,而不是用手写。
  • 示例:UserPage,FollowersSidebar,StoryContainer,FollowedUserList。

好处

  • 更好的分离问题。通过以这种方式编写组件,您可以更好地理解您的应用程序和UI。
  • 更好的可重用性。您可以使用完全不同的状态源的相同的演示组件,并将其转换为可以进一步重复使用的单独的容器组件。
  • 演示组件本质上是您的应用程序的“调色板”。您可以将它们放在一个页面上,让设计师调整所有的变体,而不用触摸应用程序的逻辑。您可以在该页面上运行截图回归测试。
  • 这迫使您提取“布局组件”,例如Sidebar,Page,ContextMenu,并使用this.props.children,而不是在多个容器组件中复制相同的标记和布局。

记住,组件不必发出DOM。他们只需要在UI问题之间提供组合界限。

何时使用容器组件

我建议您开始构建您的应用程序的时候从UI组件开始。最终你会意识到,你正在把太多的props传给中间组件。当您注意到某些组件不使用他们收到的props,而只是转发它们,并且您必须在子组件需要更多数据时重新连接所有这些中间组件,这是引入一些容器组件的好时机。这样,您可以将数据和props到子组件,而不需要在不相关组件中负担。

这是一个持续的重构过程,所以不要开始就尝试。当您尝试使用此模式时,您将开始直观的了解何时抽出一些容器,就像您知道何时提取功能一样。

其他方法

重要的是,您明​​白UI组件和容器之间的区别不是一个技术性的。相反,它的目的是区别的。

相比之下,这里有一些相关(但不同的)技术区别:

  • 有状态和无状态的。一些组件使用React setState()方法,有些则不使用。虽然容器组件往往是有状态的,而且表示组件往往是无状态的,但这并不是一个难题。演示组件可以是有状态的,容器也可以是无状态的。
  • 类和函数。由于React 0.14,组件可以被声明为类和函数。功能组件更容易定义,但它们缺少当前仅适用于类组件的某些功能。其中一些限制可能会在将来消失,但今天存在。由于功能组件更易于理解,我建议您使用它们,除非您需要状态,生命周期或性能优化,这些类型组件此时才可用。
  • 纯净和不纯。人们说如果一个组件是纯粹的,如果它保证返回相同的结果给予相同的道具和状态。纯组件可以被定义为类和函数,并且可以是有状态的和无状态的。纯组件的另一个重要方面是它们不依赖于道具或状态中的深层突变,因此它们的渲染性能可以通过其shouldComponentUpdate()钩子中的浅层比较来优化。目前只有类可以定义shouldComponentUpdate(),但可能会在将来发生更改。

两个UI组件和容器都可以落入这些存储桶中。根据我的经验,UI组件往往是无状态的纯函数,容器往往是有状态的纯类。然而,这不是一个规则,而是一个观察,我已经看到与具体情况有关的恰恰相反的情况。

不要将UI和容器组件分离作为一个教条。有时候没有关系,也很难绘制线。如果您不确定特定组件是否应该是表示性的还是集装箱的,那么决定可能还为时过早。不要着急!

延伸阅读

Getting Started with Redux
Mixins are Dead, Long Live Composition
Container Components
Atomic Web Design
Building the Facebook News Feed with Relay

原文链接

CSS Overflow Hidden在 IOS 不起作用

最近在忙调试前端CSS的工作,中间遇到一个奇葩的问题:overflow hiddenios上不起作用!其他浏览器和手机都可以,唯独Safari不行!幸好后面找到了方法,记录如下。

body {
    position:fixed;
    overflow-y:hidden;
}

这种方法在我的测试中是work的,但是可能会导致页面布局改变。

CSS:line-height:150%与line-height:1.5的真正区别是什么

语法

/* Keyword values */
line-height: normal;

/* Unitless: use this number multiplied by the element's font size */ 
line-height: 3.5;

/* <length> values */
line-height: 3em;

/* <percentage> values */
line-height: 34%;

/* Global values */
line-height: inherit;
line-height: initial;
line-height: unset;

取值

  • normal

取决于用户代理。桌面浏览器(包括火狐浏览器)使用默认值,约为1.2,这取决于元素的 font-family。

  • <number>

该属性的应用值是这个无单位数字<number>乘以该元素的字体大小。计算值与指定值相同。大多数情况下,使用这种方法设置line-height是首选方法,在继承情况下不会有异常的值。

  • <length>

指定<length> 用于计算 line box 的高度。查看<length> 获取可能的单位。

  • <percentage>

与元素自身的字体大小有关。计算值是给定的百分比值乘以元素计算出的字体大小。

形式化语法

normal | <number> | <length> | <percentage>

例子

/* All rules below have the same resultant line height */

div { line-height: 1.2;   font-size: 10pt }   /* number */ 
div { line-height: 1.2em; font-size: 10pt }   /* length */ 
div { line-height: 120%;  font-size: 10pt }   /* percentage */
div { line-height: 12pt;  font-size: 10pt }   /* length */
div { font: 10pt/1.2  Georgia,"Bitstream Charter",serif }

推荐使用无单位数值给line-height赋值

DEMO>>

代码(来自知乎):

<div style="line-height:150%;font-size:16px;">
  父元素内容
  <div style="font-size:30px;">
    Web前端开发<br />
    line-height行高问题
  </div>
</div>

下图是当line-height:150%的效果,父元素的行高为150%时,会根据父元素的字体大小先计算出行高值然后再让子元素继承。所以当line-height:150%时,字元素的行高等于16px * 150% = 24px:

!

下图是当line-height:1.5em的效果,父元素的行高为150%时,会根据父元素的字体大小先计算出行高值然后再让子元素继承。所以当line-height:1.5em时,子元素的行高等于16px * 1.5em = 24px:

!

下图是当line-height:1.5的效果,父元素行高为1.5时,会根据子元素的字体大小动态计算出行高值让子元素继承。所以,当line-height:1.5时,子元素行高等于30px * 1.5 = 45px:

!


参考:https://developer.mozilla.org/en-US/docs/Web/CSS/line-height

Redux 和 Mobx 哪个更适合你?

Redux is a predictable state container for JavaScript apps. —— Redux
Simple, scalable state management. —— Mobx

从 Redux 和 Mobx 官方的介绍很容易可以看出,它们都是用来管理应用的 state。但是它们有什么区别呢?刚好最近把之前使用 Reudx 做的 React 项目使用 Mobx 重构了,那么就来总结一下吧。

Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

redux

三大核心

在 Redux 中,最为核心的概念就是 action 、reducer、store 以及 state,那么具体是什么呢?

  • Action:是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源
  • Reducer:指明如何更新 state
  • Store:把 action、Reducer 联系到一起的对象,负责维持、获取和更新state

数据�流

严格的单向数据流是 Redux 架构的设计核心。

Redux 应用中数据的生命周期遵循下面 4 个步骤:

  • 调用 store.dispatch(action) 触发 action
  • Redux store 调用传入的 reducer 函数
  • 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
  • Redux store 保存了根 reducer 返回的完整 state 树

搭配 React

react-redux

核心库:

react
react-dom
react-router
react-router-redux  //利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件
redux
react-redux
react-thunk/react-saga/redux-logger  //middleware

目录结构:

├─app
  ├─actions //redux动作生成器
  ├─assets  //静态资源
  ├─compontens //UI组建
  ├─containers //容器组件
  ├─reducers 
  ├─routes 
  ├─store
  └─utils //工具函数
└─dist //发布目录

不过使用过 Redux 的人�会有这些痛点:难懂的 API、复杂的逻辑、过多的代码侵入。Redux 采用单一根节点、函数式编程、动作分离管理(似乎让项目很容易管理),这些都是 Redux 过于复杂的原因。然这里并不是说 Redux 不好。基于项目本身,寻找一个最适合的框架才是优的解决方案。

Mobx

MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。

比起Redux,Mobx基于观察者模式,采用多节点管理数据,是一个很轻量、入手简单、代码耦合小的数据框架。

核心概念

数据流

MobX 为单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。

mobx-fow

它由几个部分组成:Actions、State、Computed Values、Reactions。使用 MobX 将一个应用变成响应式的可归纳为以下步骤:

  • 通过事件驱动(UI 事件、网络请求…)触发 Actions
  • 在 Actions 中修改了 State 中的值,这里的 State 既应用中的 store 树(存储数据)
  • 然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值
  • 响应(react)到 UI 视图层

Observable state(可被观察的状态)

MobX 为现有的数据结构(如对象,数组和类实例)添加了可观察的功能。 通过使用 @observable 装饰器来给你的类属性添加注解就可以简单地完成这一切。这样改属性就变成了“被观察者”。

class Store {
  @observable a = 'Hello Word!';
}

Observer(观察者)

observer 函数装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。 observer 是由单独的 mobx-react 包提供的。

@observer
class Index extends Component {
  render() {
    return (
      <p>
        {this.props.Store.a}
      </p>
    )
  }
}

这样 Index 组件就变成了一个响应式的组件(智能组件),当“被观察者”改变时,该组件就会自动更新。

componentWillReact (生命周期钩子)

React 组件通常在新的堆栈上渲染,这使得通常很难弄清楚是什么导致组件的重新渲染。 当使用 mobx-react 时可以定义一个新的生命周期钩子函数 componentWillReact。当组件因为它观察的数据发生了改变,它会安排重新渲染,这个时候 componentWillReact 会被触发。这使得它很容易追溯渲染并找到导致渲染的操作(action)。

inject(注入)

inject 函数装饰器可以将 Store 数据注入到组件当中。inject 是由单独的 mobx-react 包提供的。

@inject("store")

Computed values(计算值)

使用 MobX,可以定义在相关数据发生变化时自动更新的值。 通过 @computed 装饰器调用 的getter / setter 函数来进行使用。

class ItemsStore {
  @observable items = [];

  @computed get total() {
    return this.items.length;
  }
}

当添加了一个新的 items 时,MobX 会确保 total 自动更新。

Action(动作)

action 是任一一段可以改变状态的代码。具体实现代码如下:

class HomeStore {
  @observable num = 0;

  @action plus = () => {
    this.num = ++this.num
  }
  @action minus = () => {
    this.num = --this.num
  }
}

如果是在严格模式:

import { useStrict } from 'mobx';
useStrict(true);

那么 MobX 会强制只有在动作之中才可以修改状态。对于任何不使用动作的状态修改,MobX 都会抛出异常。

异步 Action

action 包装/装饰器只会影响当前运行的函数,而不会影响当前函数调度(但不是调用)的函数! 这意味着如果你有一个 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,这些回调函数也应该包装在 action 中。可以使用 action 关键字来包装 promises 回调函数

@action
fetchData = (url) => {
  fetch(url).then(
    action('fetchRes', res => {
      return res.json()
    })).then(
    action('fetchSuccess', data => {
      // TODO

    })).catch(
    action('fetchError', e => {
      // err
    }))
}

异步 action 实现的方式还有多种,这里只列举了 action 关键子的模式

在 React 中使用 Mobx

在 React 中使用 MobX 需要用到 mobx-react。
其提供了 Provider 组件用来包裹最外层组件节点,并且传入 store 传递给后代组件:

import React from 'react';
import ReactDOM from 'react-dom';
import {useStrict} from 'mobx';
import {HashRouter as Router} from 'react-router-dom'
import {Provider} from 'mobx-react';
import * as stores from './store';

import App from './compontens/App'

useStrict(true) // 不允许在动作之外进行状态修改

ReactDOM.render(
  <Provider store={stores}>
    <Router>
      <App/>
    </Router>
  </Provider>, document.getElementById('root')
);

使用 @inject 给组件注入其需要的 store(利用 React context 机制);
通过 @observer 将 React 组件转化成响应式组件,它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件:

import React, {Component} from 'react'
import {observer, inject} from 'mobx-react'
import {withRouter} from 'react-router-dom'

@withRouter @inject('store') @observer
class App extends Component {

  render() {
    return (
      <div className="main">
        //�……
      </div>
    )
  }
}

export default App

其中 @withRouter 是 router 的 参数传入到组件中。

结束语

说到这里就会有人问,那到底哪种框架好呢?这个问题我也不知道�怎么回答是对的,各有各的优点(谁用谁知道)。

很明显 Mobx 的代码要精简得多。通过 OOP 风格和良好的开发实践,你可以快速的构建各种应用。最大的弊病是很容易编写糟糕的不可维护的代码。但是,社区发展方面 Mobx 在国内的发展还是比较缓慢的,遇到的问题可能社区都没有遇到过。

而 Redux 更受欢迎一些,而且特别适合构建大型复杂应用。这是一个规定严格的框架,其规则确保开发人员可以编写易于测试和可维护的代码。但是,的确不适合开发小项目。

两个库都非常棒。Redux 已经非常完善,Mobx 则逐渐成为一个有效的替代。

其他

项目地址

本文中中使用的源码均以开源:

欢迎交流,欢迎 Star

参考

原生JS中 forEach 和 Map 区别

最近看到关于数组遍历的东西,顺便总结一下 forEach()map() 遍历数组方法的区别。这两个方法都是 ES5 中新增的,当然说到新增方法,不能不提它们的兼容性:IE 9+,哈哈……又是 IE

原生JS forEach()和map()遍历

共同点:

  • 都可以用来便利数组的每一项
  • forEach()map() 都支持3个参数,即(item,index,Array),第一个是遍历的当前项,第二个是当前项的下标,第三个是原数组
  • 匿名函数中的this都是指windown
  • 都是智能遍历数组

forEach()

  • 没有返回值
  • forEach方法中的this是ary,匿名回调函数中的this默认是window;
  • 数组中有几项,那么传递进去的匿名回调函数就需要执行几次
  • 理论上这个方法是没有返回值的,仅仅是遍历数组中的每一项,不对原来数组进行修改;但是可以自己通过数组的索引来修改原来的数组
var ary = [12,23,24,42,1];  
var res = ary.forEach(function (item,index,input) {  
       input[index] = item*10;  
})  
console.log(res);//--> undefined;  
console.log(ary);//--> 通过数组索引改变了原数组;  

map()

[].map(); 基本用法跟forEach方法类似:

  • 回调函数中支持return返回值;return的是啥,相当于把数组中的这一项变为啥(并不影响原来的数组,只是相当于把原数组克隆一份,把克隆的这一份的数组中的对应项改变了)
var ary = [1,2,3,4,5];  
var res = ary.map(function (item,index,input) {  
    return item*10;  
})  
console.log(res);//-->[10,20,30,40,50];  原数组拷贝了一份,并进行了修改
console.log(ary);//-->[1,2,3,4,5];  原数组并未发生变化

兼容

结下来安利一下浏览器兼容的问题:

  • 不管是forEach还是map在IE6-8下都不兼容(不兼容的情况下在Array.prototype上没有这两个方法),那么需要我们自己封装一个都兼容的方法,代码如下:
/** 
* forEach遍历数组 
* @param callback [function] 回调函数; 
* @param context [object] 上下文; 
*/  
Array.prototype.myForEach = function myForEach(callback,context){  
    context = context || window;  
    if('forEach' in Array.prototye) {  
        this.forEach(callback,context);  
        return;  
    }  
    //IE6-8下自己编写回调函数执行的逻辑  
    for(var i = 0,len = this.length; i < len;i++) {  
        callback && callback.call(context,this[i],i,this);  
    }  
}  
/** 
* map遍历数组 
* @param callback [function] 回调函数; 
* @param context [object] 上下文; 
*/  
Array.prototype.myMap = function myMap(callback,context){  
    context = context || window;  
    if('map' in Array.prototye) {  
        return this.map(callback,context);  
    }  
    //IE6-8下自己编写回调函数执行的逻辑  
    var newAry = [];  
    for(var i = 0,len = this.length; i < len;i++) {  
        if(typeof  callback === 'function') {  
            var val = callback.call(context,this[i],i,this);  
            newAry[newAry.length] = val;  
        }  
    }  
    return newAry;  
}  

jQuery $.each()和$.map()遍历

共同点:

  • 即可遍历数组,又可遍历对象。

$.each()

  • 没有返回值。$.each()里面的匿名函数支持2个参数:当前项的索引i,数组中的当前项v。如果遍历的是对象,k 是键,v 是值。

$.map()

有返回值,可以return 出来。$.map()里面的匿名函数支持2个参数和$.each()里的参数位置相反:数组中的当前项v,当前项的索引 i。如果遍历的是对象,k 是键,v 是值。

原文地址

React-Native 环境安装及调试(Mac版)

准备工作

安装必要的软件,这里就不详细说了,具体可参考官方文档

  • 安装React-Native Cli npm install -g create-react-native-app

  • Xcode 安装升级到 8+

  • 初始化RN项目 react-native init rnHelloWord,可能会比较慢,要耐心等到

启动项目

  • 进入项目并启动

    cd rnHelloWord
    npm start
    

    也可以使用Xcode启动项目,点击ios/AwesomeProject.xcodeproj,再点击右上角的Run即可跑起来。

调试

模拟器调试

项目启动后,点击模拟器command+d - 选择 Enable Live Reload,监控RN代码的改动(热替换),可直接修改代码,即可查看最新的修改。

真机调试

PC上的设置

  • 手机通过USB连接电脑,�在Xcode中选择手机作为目标设备。

  • 如图填写,并选择:

  • 同时相关的 Tests target 里同样也要选择使用的开发者账号。

这样基本工作就完成了,可以点击Run启动啦~~

手机上设置

  • 这时手机上就安装了一个rnHelloWord的app,点击启动可能会弹一个‘不受信任的’提示,怎么办呢?

  • 不要着急,我们可以在手机上设置-通用-设备管理-添加信任

  • 然后就可以正常启动了

  • 应用启动后,只需要摇一摇手机可以调出调试菜单,里面具体功能同模拟器一样,这里就不再说明了。

接下来就可以开发了。


结束语

React-Native运行环境安装难于上青天,安装成功后你离成功就不远了。它的语法相近于React,撸起袖子~~~写代码吧。

使用React在不同的DOM元素中渲染组件

我刚刚开始使用React。在我的项目中,我创建了一个button。当用户点击这个创建的button时候,有些复杂的字母“a”出现在页面上。

然而,在我的html中,这个字母在按钮的兄弟元素当中,下面代码是我如何解决这个渲染元素的问题。

Attempt #1

import React from 'react';
import ReactDOM from 'react-dom';

const Letter = React.createClass({
    render() {
        return (
            // lots of HTML stuff here...
        )
    }
});

var CapitalAButton = React.createClass({
    getInitialState: function(){
        return { showCapitalA: false  };
    },

    showCapitalA: function(){
        this.setState({
            showCapitalA: true
        });
    },
    
    render: function() {
	return (
	    <div id="capital-button" onClick={this.showCapitalA}>
	        A
	        { this.state.showCapitalA? ReactDOM.render(<Letter />, document.querySelector('#letter')) : null }	    
	    </div>
	)
    }
});

var ButtonChoices = React.createClass({
    render: function() {
	return (
	    <CapitalAButton />
	)
    }
});

ReactDOM.render(
    <div id="a">
        <ButtonChoices />
    </div>,
    document.querySelector('#button-group')
);

这个问题被我在45行创建。我试图创建的按钮放在了id为button-group元素当中,而不是 container 或其他,包括任何元素本身。

结果是,在27行代码,当我最后得到渲染 <Letter /> 元素的后,我告诉React找到一个不同的Dom元素并把组件写入到里面,这个Dom 为 #letter

运行后,会得到这样的结果:

"Warning: _renderNewRootComponent(): Render methods should be a pure function of props and state; triggering nested component updates from render is not allowed. If necessary, trigger nested updates in componentDidUpdate. Check the render method of CapitalAButton."

如果一个框架运行时给出这样的错误提示,我就要注意了,所以看下面……

Attempt #2

此时,最好的方法可能是设置原始组件在 container,这个组件包含这样一个事实,就是他有一个加 button-group 的id。但是我任就是在瞎搞。

我查看了一些资料关于如何实现 componentDidUpdate,如下:

生成的代码:

import React from 'react';
import ReactDOM from 'react-dom';

const Letter = React.createClass({
  render() {
	  return (
      // same html stuff ...
	  )
  } 
});

var CapitalAButton = React.createClass({
  getInitialState: function(){
	  return { showCapitalA: false  };
  },

  componentDidUpdate: function(prevProps, prevState){
	  prevState.showCapitalA? ReactDOM.unmountComponentAtNode(document.querySelector("#letter")) : ReactDOM.render(<Letter />, document.querySelector('#letter'));
  },

  toggleCapitalA: function(){
	  this.state.showCapitalA? this.setState({showCapitalA: false}) : this.setState({showCapitalA: true})
  },
  
  render: function() {
	    return (
	      <div id="capital-button" onClick={this.toggleCapitalA}>
	      A
	      </div>
	    )
    }
});

var ButtonChoices = React.createClass({
  render: function() {
	  return (
	    <CapitalAButton />
	  )
  }
});

ReactDOM.render(
    <div id="a">
        <ButtonChoices />
    </div>,
    document.querySelector('#button-group')
);

这里有许多重大的变化。首先,这个按钮有状态,用来表示字母是否可见,当用户点击这个按钮时,状态改变。

随着状态的改变触发 componentDidUpdate 监听器,切换字母的显示与否。

结论

通过这个有趣的例子学习了React中“状态”的工作,以及一些他提供的有趣的函数。我也期待着学习如何做正确的。


有很多地方可能翻译的不准确,感兴趣的同学可以查看原文>>

查看原文:地址


我的博客

单页面应用的权限管理

在如今前端框架发展的今天,单页面应用越来越比较常见了,随之而来也将面对权限的管理。针对VUE主要说说以下方面:

  • 接口级权限
  • 页面级权限

接口级权限

接口的权限一般和UI库关系不是很大,这里用axios举例来说明。常用在管理后台这些需要获取用户登录信息的接口。这里使用axios的拦截器就能很方便的实现。

举个小例子:后台好多接口发ajax请求获取数据的时候后端(前端判断用户是否登录的手段比较少,cookie?)都会需要获取用户是否登录,并在登录的情况反馈到前端,前端跳转到登录页面。这个就可以使用这个拦截器来实现。

拦截器

在请求或响应被 thencatch 处理前拦截它们。

// 添加响应拦截器
axios.interceptors.response.use(response => {
  // 对响应数据做点什么
  return response;
}, error => {
  // 对响应错误做点什么
  return Promise.reject(error);
});
// 添加请求拦截器
axios.interceptors.request.use(config  =>{
  // 在发送请求之前做些什么
  return config;
}, error => {
  // 对请求错误做些什么
  return Promise.reject(error);
});

页面级权限

页面及权限主要靠vue-router来实现。基本思路是为全局注册一个“前置守卫”钩子函数router.beforeEach

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // 这里检查权限并进行跳转
  next()
})

每个守卫方法接收三个参数:

  • to:Route 即将要进入的目标
  • from: Route 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved

结束语

前端单页面权限控制在不同的框架中实现思路基本一致。感兴趣的同学可以去研究一下。

JavaScript 高阶函数理解与应用

认识

高阶函数(Higher Order Function)作为函数式编程众多风格中的一项显著特征,经常被使用着。

那什么是高阶函数呢?高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数,或者是返回它们,即满指至少满足下列条件之一的函数:

  • 函数作为参数被传递
  • 函数作为返回值输出

应用

实际上我们日常开发中也会经常用到高阶函数。接下来看一下几个典型的应用实例:

做为参数传递

Array.prototype.map()

map() 接受一个函数作参数,这个函数封装了创建新数组的规则,从 map() 的使用可以看到,我们的目的是根据原数组获取一个新数组,这是不变的部分;而使用什么规则去创建,则是可变的部分。把可变的部分封装在函数参数里,动态传入 map(),使 map() 方法成为了一个非常灵活的方法。

const arr1 = [1,3,5]
const arr2 = arr1.map(item => tem*2)

console.log(arr2) //2,6,10

回调函数

const getInfo = (url, callback) => {
  fetch(url).then( res => {
    callback(res)
  })
  .catch( err => {
    alert(err.msg)
  })
}

const _url = '/api/get'
getInfo(_url, data => {
    console.log(data.name);
});

作为返回值

判断数据的类型

const TYPE = type => obj => Object.prototype.toString.call( obj ) === `[object ${type}]`

const isArray = TYPE('Array');
const isNumber = TYPE('Number');
const isString = TYPE('String');

console.log(isArray([1,2,3])) // 输出:true
console.log(isString('str')) // 输出:true

// ES5
/*
var TYPE = function(type){
  return function (obj){
    return Object.prototype.toString.call( obj ) === '[object '+type+']'
  }
}
*/

在这里,TYPE() 做为高阶函数,返回一个匿名函数。

结束语

高阶函数并不是 javaScript 的专利,但绝对是 javaScript 编程的利器。高阶函数实际上就是对基本算法的再度抽象,我们可以利用这一点,提高代码的抽象度,实现最大限度的代码重用,编写出更简洁、更利于重构的代码。

拓展

高阶组件

高阶组件(HOC)是 React 生态系统的常用词汇,React 中代码复用的主要方式就是使用高阶组件,并且这也是官方推荐的做法。而 Vue 中复用代码的主要方式是使用 mixins,并且在 Vue 中很少提到高阶组件的概念,这是因为在 Vue 中实现高阶组件并不像 React 中那样简单,原因在于 React 和 Vue 的设计**不同,但并不是说在 Vue 中就不能使用高阶组件,只不过在 Vue 中使用高阶组件所带来的收益相对于 mixins 并没有质的变化。

修饰器 Decorator

修饰器是一个对类进行处理的函数。

感兴趣的同学可以自己去研究高阶组件(HOC)和 Decorator,这里不展开说明了。

h5文档规范(kaowo)

一.目录结构

目录结构

  • App—存放需要引入jQuery的项目;
  • spApp—存放不需要引入jQuery的项目;
  • 特殊情况的项目需要单独新建文件夹,类似share项目;
├── partial                    //脚本
│   └── active-partial         //具体页面          
├── services                   //服务
│   └── services.js     
└── spapp.js                   //路由文件 

二.生产环境

H5项目是基于Grunt+bower+AngularJs

Gruntfile.js

- build-ngapp (在dist中生成ngapp项目压缩的代码)
- build-share (在dist中生成share项目压缩的代码)
- build-spapp (在dist中生成spapp项目压缩的代码)
- run-dev (本地未压缩环境服务启动命令)
- run-dist (本地已压缩环境服务启动命令)

三.命名规则

1.项目名

项目名全部采用小写方式, 以中划线分隔。 比如:kaowo-intro

2.HTML文件命名

采用小写方式,多个单词组成时,采用中划线连接方式,比如说: kaowo-intro.html

3.js文件命名

  • 控制器:小写+Ctrl,比如说:procertiCtrl.js
  • 服务:项目简称+Services,比如说:spService.js
  • 路由:项目名称,比如说:spapp.js

4.css文件命名

  • 采用小写方式,多个单词组成时,采用中划线连接方式,比如说: kaowo-intro.css

四.语法规则

1.HTML

语法

  • 使用四个空格的 soft tabs — 这是保证代码在各种环境下显示一致的唯一方式。
  • 嵌套的节点应该缩进(四个空格)。
  • 在属性上,使用双引号,不要使用单引号。
  • 不要在自动闭合标签结尾处使用斜线 - HTML5 规范 指出他们是可选的。
  • 不要忽略可选的关闭标签(例如,
  • 和 )。

HTML5 doctype

  • 在每个 HTML 页面开头使用这个简单地 doctype 来启用标准模式,使其每个浏览器中尽可能一致的展现。
  • 虽然doctype不区分大小写,但是按照惯例,doctype大写 关于html属性,大写还是小写的一片文章

字符编码

  • 通过声明一个明确的字符编码,让浏览器轻松、快速的确定适合网页内容的渲染方式。
<head>
    <meta charset="UTF-8">
</head>

属性顺序

  • HTML 属性应该按照特定的顺序出现以保证易读性。
id
class
name
data-*
src, for, type, href
title, alt
aria-*, role
  • Classes 是为高可复用组件设计的,理论上他们应处在第一位。Ids 更加具体而且应该尽量少使用(例如, 页内书签),所以他们处在第二位。但为了突出id的重要性, 把id放到了第一位。

减少标签数量

  • 在编写 HTML 代码时,需要尽量避免多余的父节点。很多时候,需要通过迭代和重构来使 HTML 变得更少。 参考下面的示例:
<!-- Not so great -->
<span class="avatar">
    <img src="...">
</span>

<!-- Better -->
<img class="avatar" src="...">

2.css

H5项目中所有的css全部使用less通过grunt编译生成。

语法

  • 使用组合选择器时,保持每个独立的选择器占用一行。
    为了代码的易读性,在每个声明的左括号前增加一个空格。
  • 声明块的右括号应该另起一行。
  • 尽可能使用短的十六进制数值,例如使用 #fff 替代 #ffffff。
  • 为选择器中的属性取值添加引号,例如 input[type="text"]。 他们只在某些情况下可有可无,所以都使用引号可以增加一致性。
  • 不要为 0 指明单位,比如使用 margin: 0; 而不是 margin: 0px;。

声明顺序:

  1. Positioning
  2. Box model 盒模型
  3. Typographic 排版
  4. Visual 外观

Positioning 处在第一位,因为他可以使一个元素脱离正常文本流,并且覆盖盒模型相关的样式。盒模型紧跟其后,因为他决定了一个组件的大小和位置。

*Class 命名 *

  • 保持 Class 命名为全小写,可以使用短划线(不要使用下划线和 camelCase 命名)。短划线应该作为相关类的自然间断。(例如,.btn 和 .btn-danger)。
  • 避免过度使用简写。.btn 可以很好地描述 button,但是 .s 不能代表任何元素。
  • Class 的命名应该尽量短,也要尽量明确。
  • 命名时使用最近的父节点或者父 class 作为前缀。
  • 使用 .js-* classes 来表示行为(相对于样式),但是不要在 CSS 中包含这些 classes

3.javaScript

完全避免 == != 的使用, 用严格比较条件 === !==

*缩进 分号 空行 *

  • 一律使用4个空格
  • Statement 之后一律以分号结束, 不可以省略
  • 方法之间加
  • 单行或多行注释前加
  • 逻辑块之间加空行增加可读性

*变量 常量 *

  • 标准变量采用驼峰标识
  • 使用的ID的地方一定全大写
  • 使用的URL的地方一定全大写, 比如说 reportURL
  • 构造函数,大写第一个字母
  • 一般情况下统一使用 '' 单引号

*Object Literals *

// Good  semi colon 采用 Followed by space 的形式
var team = {
    title: "AlloyTeam",
    count: 25
};

*Array Literals *

// Good
var colors = [ "red", "green", "blue" ];
var numbers = [ 1, 2, 3, 4 ];

*注释 *

  • 单行注释双斜线后,必须跟注释内容保留一个空格
  • 单行注释可独占一行, 前边不许有空行, 缩进与下一行代码保持一致
  • 可位于一个代码行的末尾,注意这里的格式
  • 多行注释最少三行, 格式如
/*
 * 注释内容与星标前保留一个空格
 */

函数声明

  • 一定先声明再使用, 不要利用 JavaScript engine的hoist特性, 违反了这个规则 JSLint 和 JSHint都会报 warn
  • function declaration 和 function expression 的不同,function expression 的()前后必须有空格,而function declaration 在有函数名的时候不需要空格, 没有函数名的时候需要空格。
  • 函数调用括号前后不需要空格
  • 立即执行函数的写法, 最外层必须包一层括号
  • "use strict" 决不允许全局使用, 必须放在函数的第一行, 可以用自执行函数包含大的代码段, 如果 "use strict" 在函数外使用, JSLint 和 JSHint 均会报错
function doSomething(item) {
    // do something
}
var doSomething = function (item) {
    // do something
}

// Good
doSomething(item);
// Bad: Looks like a block statement
doSomething (item);

// Good
var value = (function() {
    // function body
    return {
        message: "Hi"
    }
}());

// Good
(function() {
    "use strict";
    function doSomething() {
        // code
    }
    function doSomethingElse() {
        // code
    }
})();

webpack中的神器 webpack-glob-entries

今天来介绍一个webpack中的神器级的插件 webpack-glob-entries

为什么要用它

  • 先来看看下面的代码
entry: {
        pageA: "./pageA",
        pageB: "./pageB",
        pageC: "./pageC",
        adminPageA: "./adminPageA",
        adminPageB: "./adminPageB",
        adminPageC: "./adminPageC",
        ……
    },
  • 这样的缺点

当项目很庞大的时候,入口文件 entry 方法中将引入很多的 js 页面就像上面一样,很难管理,而且每次新增一个 js 文件,都要去 webpack 中添加一条入口记录

遇到这种情况,你想想怎么办?是不是就想让 webpack 自动去检测 js 文件,而不是手动的添加

神器就这样诞生了

怎么用

var glob_entries = require('webpack-glob-entries');
entry: glob_entries('/src/scripts/*.js')

完工

妈妈再也不用担心我使用webpack打包了

学会这个,前端也可以和PHP程序员一样了

话说 PHP 是世界上“最好”的语言,我不是 PHPer ,所以今天我们的主角不是 PHP ,而是前端(Nextjs)。那么问题来了,Nextjs 是什么?

Next.js is a lightweight framework for static and server-rendered applications.

说直白了:Next.js 是一个基于 React 实现的服务端渲染框架。
好了,今天我们就来聊聊 Next.js 实现。

介绍

该项目通过使用 Nextjs 技术,实现了 React 同构方案。采用 Nodejs 搭建服务,结合 Mongoose 数据库,实现了一个简单的博客系统。
也可以参考项目 v1.0 版本通过 Ejs 模版的实现,相关文章>>

技术实现

  • Express
  • mongoose
  • react > 16.x
  • Nextjs
  • Node > 8.x
  • sass
  • isomorphic-unfetch

目录结构

├─server # 服务
│  ├─controllers # 控制器
│  ├─dto  #
│  ├─models # 模型
│  ├─routes  # 路由
│  └─service
├─pages # 页面
│  ├─…… #
│  └─index.js # 主页面
├─compontents # 组件
│  └─#……
├─config # 配置文件
│  └─#……
├─assets # 静态资源
│  └─#……
├─build # 发布目录
│  └─ #……
├─next.config.js # next配置文件
├─package.json
├─postcss.config.js # postcss配置
├─server.js # 服务入口文件
└─.babelrc

如何使用

主要针对我当前的项目>> node-blog-app 来说说使用方法:

Install

git clone https://github.com/Hancoson/node-blog-app.git

yarn

Install mongodb

brew install mongodb

Start Mongo

mongod
# or
brew services start mongodb

最后执行:

mongo

Create a datebase

use {nodeApp}

Start App

npm run dev #(start dev)

npm run build #(构建)

npm start #(启动项目)

遇到问题

项目开始的时候,也是遇到(踩)了不少的问题(坑)。接下来捡几个来说说吧。

编译

最开始 clone 了官方的 demo ,但是编译(npm run dev)的时候会遇到 (node:58300) UnhandledPromiseRejectionWarning: Error: Cannot find module 'babel-plugin-transform-runtime' 报错,后来查了资料,也在 issues 上提了,官方给我的答复是过时的 .babelrc 影响。哎~自己都浪费了半天时间,按照大神 @timneutkens 的指导,添加了新的 .babelrc ,解决了此问题。

//.babelrc
{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

获取数据

Nextjs 提供一个新的生命周期函数 getInitialProps 。具体用法如下:

import React from 'react'
export default class extends React.Component {
  static async getInitialProps({ req }) {
    //这里可以使用 isomorphic-unfetch 获取 server 端数据
    const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
    return { userAgent }
  }

  render() {
    return (
      <div>
        Hello World {this.props.userAgent}
      </div>
    )
  }
}

在页面渲染加载数据时,我们使用 getInitialProps 这个异步静态方法来获取 props (类似JSON.stringify)对象。初始化页面的时候,getInitialProps 方法只会在服务端执行。

全新的 React Context API

在一个经典的 React 应用中,组件之间通信是常用到的技术方案。在父子组件之间通常通过 props 来传递参数,而非父子组件就比较麻烦了,要么就一级一级通过 props 传递,要么就使用 Redux or Mobx 这类状态管理的状态管理库,但是这样无疑增加了应用的复杂度。在 FEers 的期盼中,React 团队终于从 16.3.0 版本开始新增了一个新的 API Context,福音啊。好了,今天我就来一起学习一下这个新的 Context

什么时候使用 Contsxt

Context 目的是为了共享可以被认为是 React 组件“全局”树的数据。例如当前应用的主题、首选语言等等。接下来看看通过 propsContext 两种方式实现按钮组件样式参数传递方式的对比:

  • props
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}
Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}
ThemedButton(props) {
  return <Button theme={props.theme} />;
}
  • Context
// 创建 context 实例
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}
Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

API

React.createContext

创建一个 ContextReact.createContext 提供了 {Provider,Comsumer} 两个方法,上面的代码也可以这个来写:

const {Provider,Comsumer} = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <Provider value="dark">
        {/* ... */}
      </Provider>
    );
  }
}
{/* ... */}
ThemedButton(props) {
  return (
    <Consumer>
      {/* ... */}
    </Consumer>
  );
}

Provider

这里的 Provider 类似 react-redux 中的 Provider 组件,用来注入全局的 data (允许 Consumer 订阅 Context 的变化)。一个 Provider 可以连接到多个 Consumer

Consumer

Consumer 组件,表示要消费 Provider 传递的数据(订阅 Context 的响应组件)。当 Provider 发生变化的时候,所有的 Consumer 都会被 re-rendered

结束语

Context 的引入,一定程度上可以减少不少项目对 redux 全家桶的依赖,从而降低了项目的复杂程度,何乐而不为呢~~

一个简单的Node应用的开发历程

最近终于有时间�静下心来学学node相关的知识了,那么来做一个什么东西呢?想来想去还是来做一个简单的bolg吧~~。接下来就介绍一下具体的历程。

技术栈

  • node
  • express(web应用框架)
  • mongoose(数据库)
  • pm2(应用进程管理)
  • swagger(API功能调试)

项目介绍

本文章将用具体的项来介绍 node-app:https://github.com/Hancoson/node-app

文件结构

  • app.js:入口文件
  • package.json:工程信息及包管理
  • node_moudules:依赖模块
  • public:静态资源(css、js、images)
  • routes:路由管理
  • view:模版文件
  • models:数据模型
  • config:项目配置文件
  • app:存放�控制器、公用方法等
  • bin:项目配置脚本

MVC模式

  • Model
    • node提供的模块,中间件,在用express创建项目时,产生node_modules即表示M
    • 模块如ejsmongoosemorganbody-parser等等
  • View
    • express生成项目时会产生views,即前端
  • Controller
    • 即视图向控制器发出请求,由控制器选择相应的模型来处理
    • 模型返回的结果给控制器,由控制器来选择合适的视图,生成界面给用户
    • 如通过res.render来渲染ejs文件

路由

  • 意义 : 访问主页时调用ejs模板引擎渲染index.ejs文件
  • 实现方法 :
    • app.js中写入require('./routes/index')(app)即可引入�;
    • 路由中在引入对应的Controller来实现具体数据的展示app.get('/articles/:id', articles.getArticle);

Controller

主要来处理业务逻辑,也就是说数据该怎么展示由他来管理,具体实现如下:

function (req, res) {
  blogdbs.find({
    _id: req.params.id //查询条件
  }, function (err, data) {
    if (err) {
      //err
    } else {
      res.render('articles', {
        ... //数据对象
      });

    }
  })
}

Model

Model前先说说Schema,那么Schema是什么呢?它类似于关系数据库的表结构。具体实现如下:

var mongoose = require('mongoose');
var schema = mongoose.Schema;
var _blogSchema = new Schema({
  title: {
    type: 'String'
  },
  ...
});

接下来就可以创建Model了,格式是mongoose.model(modelName, schema);

Schema后为什么还要有Model呢?

Mongoose的设计理念中,Schema用来也只定义数据结构,具体对数据的增删改查操作都由Model来执行。

就好比富士康用模具组装出一台手机后,当需要打电话时是用手机来打而不是用模具。

遇到的问题

  • node开发过程中需要�频繁的重启项目,后来学习解了pm2来管理进程,并可监控node服务端的更新,不用再重启服务了,开发效率大大提高。

  • 在服务端异步提交数据时候,app.js中需要加入以下代码,并且需要在路由引入的上方。

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: false}));
    
  • app.get()app.use()app.all()

    • app.use(path,callback)中的callback既可以是router对象又可以是函数
    • app.get(path,callback)中的callback只能是函数,可以将 app.get() 看做 app.use() 的请求 get 方式的简要写法。
    • app.all() 附加到应用程序的路由,所以使用 app.router 中间件负责处理所有的路由请求,如:GET、POST等;
    var express = require('express');
    var app = express();
    app.get('/hello',function(req,res,next){
      res.send('hello test2');
    });
    
    //等同于:
    var express = require('express');
    var app = express();
    var router = express.Router();
    router.get('/', function(req, res, next) {
      res.send('hello world!');
    });
    app.use('/hello',router);
    
    

    如果您觉得阅读本文对您有帮助,请“Star”,您的“Star”将是我最大的写作动力!

    项目地址:node-app

Async/await 函数简介

自从 ES6 诞生以来,异步编程的方法得到了很大的发展。从 Promise 到 ES7 提案中的 async/await。目前,它仍处于提案阶段,async 函数可以说是目前异步操作最好的解决方案,是对 Generator 函数的升级和改进。那么今天就来具体说说 async/await 函数。

async/await 简介

  • async/await 是异步代码的新方式
  • async/await 基于 Promise 实现
  • async/await 使得异步代码更像同步代码
  • await 只能用在 async 函数中,不能用在普通函数中
  • await 关键字后面必须跟 Promise 对象
  • 函数执行到 await 后,Promise 函数执行完毕,但因为 Promise 内部一般都是异步函数,所以事件循环会一直 wait,直到事件轮询检查到 Promise 有了状态 resolve 或 reject 才重新执行这个函数后面的内容

async/await 用法

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回一个 Promise 对象,等到异步操作完成,再接着执行函数体内后面的语句。

function timeout(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}
async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}
asyncPrint('hello world', 50);

错误处理

  • 方法一

    function timeout(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      });
    }
    async function asyncPrint(value, ms) {
      await timeout(ms);
      return value
    }
    asyncPrint('hello world', 5000).then(result=> {
      console.log(result);
    }).catch(err=>{
      console.log(err)
    })
  • 方法二

    function timeout(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      });
    }
    async function asyncPrint(value, ms) {
      try{
        await timeout(ms);
        console.log(value)
      }catch(err){
        console.log(err)
      }
      await timeout(ms);
      return value
    }
    asyncPrint('hello world', 5000)
  • await 后面紧跟着的最好是一个耗时的操作或者是一个异步操作(当然非耗时的操作也可以的,但是就失去意义了)
  • 如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject

async 函数的学习到这里就结束了,但是这并不意味着 async 的用法只有这些,我们在学习了基础以后,更要把它与其他的知识相结合起来,才能写出更可靠更优质的代码!

移动端前端适配方案对比

最近抽空看了看移动端适配的一些文章,也结合自己的经验做一下总结以及对比。

那么,开始正题,首先说说到目前位置出现的一些关于移动端适配的技术方案:

  • 通过媒体查询的方式即CSS3meida queries
  • 以天猫首页为代表的 flex 弹性布局
  • 以淘宝首页为代表的 rem+viewport缩放
  • rem 方式

Meida Queries

meida queries 的方式可以说是我早期采用的布局方式,它主要是通过查询设备的宽度来执行不同的 css 代码,最终达到界面的配置。核心语法是:

@media screen and (max-width: 600px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/
  /*你的css代码*/
}

!Meida Queries

优点

  • media query可以做到设备像素比的判断,方法简单,成本低,特别是对移动和PC维护同一套代码的时候。目前像Bootstrap等框架使用这种方式布局
  • 图片便于修改,只需修改css文件
  • 调整屏幕宽度的时候不用刷新页面即可响应式展示

缺点

  • 代码量比较大,维护不方便
  • 为了兼顾大屏幕或高清设备,会造成其他设备资源浪费,特别是加载图片资源
  • 为了兼顾移动端和PC端各自响应式的展示效果,难免会损失各自特有的交互方式

Flex 弹性布局

以天猫的实现方式进行说明:

它的viewport是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

!Flex

高度定死,宽度自适应,元素都采用px做单位。

随着屏幕宽度变化,页面也会跟着变化,效果就和PC页面的流体布局差不多,在哪个宽度需要调整的时候使用响应式布局调调就行(比如网易新闻),这样就实现了『适配』。DEMO>>

rem+viewport 缩放

这也是淘宝使用的方案,根据屏幕宽度设定 rem 值,需要适配的元素都使用 rem 为单位,不需要适配的元素还是使用 px 为单位。

实现原理

根据rem将页面放大dpr倍, 然后viewport设置为1/dpr.

  • 如iphone6 plus的dpr为3, 则页面整体放大3倍, 1px(css单位)在plus下默认为3px(物理像素)
  • 然后viewport设置为1/3, 这样页面整体缩回原始大小. 从而实现高清。

!viewport

!viewport

这样整个网页在设备内显示时的页面宽度就会等于设备逻辑像素大小,也就是device-width。这个device-width的计算公式为:

设备的物理分辨率/(devicePixelRatio * scale),在scale为1的情况下,device-width = 设备的物理分辨率/devicePixelRatio

具体请查看 https://github.com/amfe/lib-flexiblehttps://www.npmjs.com/package/anima-hd.

rem 实现

说说我司【魅族】移动端的实现方式,viewport也是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

通过代码来控制rem基准值(设计稿以720px宽度量取实际尺寸)

css通过sass预编译,设置量取的px值转化rem的变量$px: (1/100)+rem;;

具体实现方案可以查看 >> mpx2rem

!rem

1像素边框高清

淘宝实现方式

上面说到的淘宝的实现方式即rem+viewport 缩放来实现。

transform: scale(0.5)

CSS代码:

div{
    width: 1px;
    height: 100%;
    display: block;
    border-left: 1px solid #e5e5e5;
    -webkit-transform: scaleX(.5);
    transform: scaleX(.5);
}

缺点:

  • 圆角无法实现,实现4条边框比较麻烦,并且只能单独实现,如果嵌套,会对包含的效果产生不想要的效果,所以此方案配合:after和before独立使用较多。

box-shadow

实现方法:

利用CSS对阴影处理的方式实现0.5px的效果。

-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);

优点:

基本所有场景都能满足,包含圆角的button,单条,多条线。

缺点:

  • 颜色不好处理, 黑色 rgba(0,0,0,1) 最深的情况了。有阴影出现,不好用。
  • 大量使用box-shadow可能会导致性能瓶颈。
  • 四条边框实现效果不理想。

图片实现

使用 background-image 实现1px有两种方式: 渐变 linear-gradient 或直接使用图片(base64)。

渐变 linear-gradient (50%有颜色,50%透明)

单条线:

div {
  height: 1px;
  background-image: -webkit-linear-gradient(top,transparent 50%,#000 50%);
  background-position: top left;
  background-repeat: no-repeat
  background-size: 100% 1px;
}

多条线:

div {
  background-image:-webkit-linear-gradient(top, transparent 50%, #000 50%),-webkit-linear-gradient(bottom, transparent 50%, #000 50%),-webkit-linear-gradient(left, transparent 50%, #000 50%),-webkit-linear-gradient(right, transparent 50%, #000 50%);
  background-size: 100% 1px,100% 1px,1px 100%,1px 100%;
  background-repeat: no-repeat;
  background-position: top left, bottom left, left top, right top;
}

优点:

  • 可以设置单条,多条边框
  • 可以设置颜色

缺点:

  • 大量使用渐变可能导致性能瓶颈
  • 代码量大
  • 多背景图片有兼容性问题

Angular6 开发探索 - 知乎日报

最近利用空余时间研究学习了前端“三剑客”之 —— Angular。Angular 是一个开发平台。它能帮你更轻松的构建 Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。Angular 为开发者提升构建 Web、手机或桌面应用的能力(好官方~~~)。好了,接下来结合自己做 DEMO 的过程来说说。

github >> https://github.com/Hancoson/ng-zhihuDaily Star Star Star ~~

开始

运行

安装

全局安装 Angular CLI

npm install -g @angular/cli

新建项目

ng new {my-app}

启动

cd my-app
ng serve --open

或者你可直接克隆本实例

git clone https://github.com/Hancoson/ng-zhihuDaily.git

# 1
npm i

# 2
npm start

Angular CLI 使用

添加功能

ng g cl my-new-class #新建 class
ng g c my-new-component #新建组件
ng g d my-new-directive #新建指令
ng g e my-new-enum #新建枚举
ng g m my-new-module #新建模块
ng g p my-new-pipe #新建管道
ng g s my-new-service #新建服务
ng g m route --routing #新建 route

说明:

  • g - generate
  • cl - class
  • c - component
  • d - directive
  • e - enum
  • m - module
  • p - pipe
  • s - service

备注

不生成单元测试文件

 --spec=falses

设置默认样式

希望自己的项目使用 Scss 或者 Less 的时候怎么办呢?看看下面:

新建项目:

ng new {project-name} --style=scss  

在已有项目中设置:

手动修改angular.json文件,添加如下内容即可:

"schematics": {  
  "@schematics/angular:component": {  
       "styleext": "scss"  
   }  
 },

结构

├── README.md
├── angular.json # Angular CLI 的配置文件。
├── dist # 构建目录
├── e2e # 在 e2e/ 下是端到端(end-to-end)测试
├── node_modules # 依赖
├── package.json
├── src
│   ├── app
│   │   ├── app.component.css  #根组件样式
│   │   ├── app.component.html  #根组件模版
│   │   ├── app.component.spec.ts #根组件单元测试
│   │   ├── app.component.ts  # 根组件
│   │   ├── app.module.ts # 定义 AppModule,根模块为 Angular 描述如何组装应用
│   │   ├── components # 组件集
│   │   │   └── home
│   │   │       ├── home.component.html # 组件模版
│   │   │       ├── home.component.scss # 组件样式
│   │   │       ├── home.component.spec.ts # 组件单元测试
│   │   │       └── home.component.ts # 组件逻辑
│   │   ├── constants # 项目静态配置
│   │   └── route # 路由
│   ├── assets # 静态资源
│   ├── browserslist # 一个配置文件,用来在不同的前端工具之间共享目标浏览器
│   ├── environments # 各环境配置文件
│   ├── index.html # 主页面的 HTML 文件
│   ├── karma.conf.js # 给Karma的单元测试配置
│   ├── main.ts # 应用的主要入口点
│   ├── pipe # 管道/过滤器
│   ├── polyfills.ts # 低版本浏览器支持配置
│   ├── styles.scss # 这里是你的全局样式
│   ├── test.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.spec.json
│   ├── tslint.json # 额外的 Linting 配置
│   └── utils # 工具函数
├── tsconfig.json # TypeScript 编译器的配置
└── tslint.json # 给TSLint和Codelyzer用的配置信息

主要技术点

模块

Angular 应用是模块化的,它拥有自己的模块化系统,称作 NgModule。 一个 NgModule 就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、某个工作流或一组紧密相关的功能。 它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义。 它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它 NgModule 使用。

NgModule 是一个带有 @NgModule 装饰器的类。@NgModule 装饰器是一个函数,它接受一个元数据对象,该对象的属性用来描述这个模块。其中最重要的属性如下。

  • declarations(可声明对象表) —— 那些属于本 NgModule 的组件、指令、管道。
  • exports(导出表) —— 那些能在其它模块的组件模板中使用的可声明对象的子集。
  • imports(导入表) —— 那些导出了本模块中的组件模板所需的类的其它模块。
  • providers —— 本模块向全局服务中贡献的那些服务的创建器。 这些服务能被本应用中的任何部分使用。(你也可以在组件级别指定服务提供商,这通常是首选方式。)
  • bootstrap —— 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个 bootstrap 属性。
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

组件

组件控制屏幕上被称为视图的一小片区域。

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
  /*
  ……
  */
}
  • selector:是一个 CSS 选择器,它会告诉 Angular,一旦在模板 HTML 中找到了这个选择器对应的标签,就创建并插入该组件的一个实例。
  • templateUrl:该组件的 HTML 模板文件相对于这个组件文件的地址。
  • providers:是当前组件所需的依赖注入提供商的一个数组。
  • styleUrls:组件所需的样式资源。

路由

路由定义 会告诉路由器,当用户点击某个链接或者在浏览器地址栏中输入某个 URL 时,要显示哪个视图。

import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './../components/home/home.component';

const myRoute: Routes = [
  { path: '', component: HomeComponent }
];

export const appRoutes = RouterModule.forRoot(
  myRoute,
  { enableTracing: true }
)

典型的 Angular 路由(Route)有两个属性:

  • path:一个用于匹配浏览器地址栏中 URL 的字符串。
  • component:当导航到此路由时,路由器应该创建哪个组件。

RouterModule.forRoot():初始化路由器,并让它开始监听浏览器中的地址变化

管道

切割字符串

  • 新建管道文件

    import { Pipe, PipeTransform } from '@angular/core'
    
    @Pipe({
      name: 'SliceStr'
    })
    
    export class SliceStrPipe implements PipeTransform {
      // start和end及extraStr后面都跟随了一个问好,代表这个为可选参数而不是必选的
      // 功能就是给出截图的启示,然后是否拼接你自定义的字符串(...)等
      transform(value: string, start?: number, end?: number, extraStr?: string): string {
        if (value) {
          if (typeof (start) === 'number' && typeof (end) === 'number') {
            if (value.length > end && extraStr && typeof (extraStr) === 'string') {
              return value.slice(start, end) + extraStr.toString();
            } else {
              return value.slice(start, end);
            }
          } else {
            return value;
          }
        } else {
          return value;
        }
      }
    }
  • 引入

    import { SliceStrPipe } from '../pipe/slicestr.pipe'
    // ...
    @NgModule({
      declarations: [
        SliceStrPipe
      ],
      // ...
    })
  • 使用

    <p class="title">{{item.title|SliceStr: 0:20:'...'}}</p>

相关项目参考


参考资料:

npm vs yarn

npm 和 yarn 是目前比较常用的包管理工具。本文主要对 npm5+ 和 yarn 来做简单的对比和介绍。

npm 与 yarn 主要的命令对比:

npm命令 yarn命令
npm install yarn (install)
npm install --save / npm i yarn add
npm install --save-dev / npm i -D yarn add --dev
npm uninstall yarn remove
npm init yarn init
npm install -g yarn global add
npm uninstall -g yarn global remove
npm start yarn start
npm run yarn run
npm ls yarn list

yarn

yarn是2016年10月发布,目前在Github上获取了3.4w+的 star。官方给出的解释是:快速、可靠、安全的依赖管理。

特点

  • 并行安装:无论 npm 还是 yarn 在执行包的安装时,都会执行一系列任务。npm 是按照队列执行每个 package,也就是说必须要等到当前 package 安装完成之后,才能继续后面的安装。而 yarn 是同步执行所有任务,提高了性能。
  • 离线模式:如果之前已经安装过一个软件包,再次安装时就不用再从网络下载了。
  • 安装版本统一:安装版本统一为了防止拉取到不同的版本,yarn 有一个锁定文件 (lock file) 记录了被确切安装上的模块的版本号。每次只要新增了一个模块,Yarn 就会创建(或更新)yarn.lock 这个文件。这么做就保证了,每一次拉取同一个项目依赖时,使用的都是一样的模块版本。
  • 多注册来源处理:所有的依赖包,不管他被不同的库间接关联引用多少次,安装这个包时,只会从一个注册来源去装,要么是 npm 要么是 bower, 防止出现混乱不一致

npm5+

node 升级到了 8.0 后 npm 也随之升到 5.0,相比与老的 npm,npm5 有了巨大的变化,

特性

  • 锁文件(lockfile):新增了 package-lock.json 文件,在操作依赖时默认生成,用于记录和锁定依赖树的信息。使用过 yarn 的同学应该能感觉到,这里估计也是在 yarn 的 lockfile 大受欢迎的背景下做出了这个修改。
  • 缓存优化:新版本重写了整个缓存系统,缓存将由 npm 来全局维护(~/.npm)不用用户操心,这点也是在向 yarn 看齐。
  • Git 依赖支持优化:对 Git 依赖支持了通过 semver 版本号安装指定的版本。例如可以通过以下命令安装 Github 上的 chalk 1.0.0 版本,这个特性在需要安装大量内部项目(例如在没有自建源的内网开发),或需要使用某些依赖的未发布版本时很有用。
    npm install git+https://github.com/chalk/chalk.git#semver:1.0.0
    
  • 文件依赖优化:在之前的版本,如果将本地目录作为依赖来安装,将会把文件目录作为副本拷贝到 node_modules 中。而在 npm5 中,将改为使用创建 symlinks 的方式来实现(使用本地 tarball 包除外),而不再执行文件拷贝。这将会提升安装速度。
  • npx:npm v5.2.0 引入的一条命令(npx),npx 会帮你执行依赖包里的二进制文件。引入这个命令的目的是为了提升开发者使用包内提供的命令行工具的体验。在以往中,我们在 node 项目中要执行一个脚本,需要将它在 scripts 中声明
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "init:runtime-only": "vue init webpack vue-cms"
    }
    
    然后需要执行命令
    npm run init:runtime-only
    
    用了 npx 以后呢,你不需要在 scripts 中声明了,就可以直接敲键盘了:
    npx vue init webpack vue-cms
    

速度对比

下面是测试了npm 4.6.1,npm 5.5.1和 yarn安装速度的对比:来源

jietu20181224-150021 2x

通过对比可以看出,npm 从 4 到 5,有了很大的飞跃,yarn与npm5对比速度在大部分正常场景下还是略高一筹,特别是有缓存的情况下,不过相比之下 npm5 的差距已经很小。

总结

通过以上一系列对比,我们可以看到 npm5 在速度和使用上确实有了很大提升。但从速度上来说 yarn 貌似还是更快一点。但是 npm 社区似乎更加的活跃,后续的发展也很值得期待的。

其他

NPM依赖包版本号~和^和*的区别

  • 会匹配最近的小版本依赖包,比如1.2.3会匹配-所有1.2.x版本,但是不包括1.3.0
  • ^会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0
  • *这意味着安装最新版本的依赖包

推荐使用~,只会修复版本的bug,比较稳定。使用^ ,有的小版本更新后会引入新的问题导致项目不稳定。
或者版本号写*,这意味着安装最新版本的依赖包,但缺点同上,可能会造成版本不兼容,慎用!

参考资料

My frist Vue project

介绍

Vuejs火了有一段时间了,但是之前自己还没有一个线上项目中使用它。刚搞公司前一段时间有个新项目要开发,再加上大BOSS那边要求使用Vuejs来做,自己也就毫不犹豫的接了下来,并用了Vuejs作为开发框架。那为什么要用它呢?有以下原有:

  • 易用/灵活/性能(哈哈~来自官网)
  • 对自身的技术面的拓展和提升
  • 后台项目,表单编辑比较多,很适合使用双向绑定的方式来控制dataview
  • 公司层面希望技术栈统一

过程

技术

1.构建工具

构建工具方面参考了尤大大的[vue-cli](https://github.com/vuejs/vue-cli)vue-element-admin(https://github.com/PanJiaChen/vue-element-admin),当然也可以看看自己的小(DEOM)[https://github.com/Hancoson/vue-manage]。也基本都是copy第三方的,没有自己去造轮子。

2.技术栈

使用到的技术栈如下:
技术栈图

目录结构:


├─index.html      //页面入口
├─build           //构建配置
├─config          //构建配置文件
├─package.json    
├─yarn.lock
├─README.md
└─src             //资源目录
    │  App.vue    //vue入口
    │  main.js    //总入口文件
    ├─assets      //静态资源目录
    │  ├─img      
    │  ├─mock     //mock数据
    │  └─scss
    ├─components  //组件目录
    │  ├─common   //公用组件
    │  │  ├─home
    │  │  │      home.vue
    │  │  ├─layout
    │  │  │      header.vue
    │  │  └─……  
    │  └─pages    //页面
    │      ├─index
    │      │      index.vue
    │      └─……
    ├─router      //路由
    └─utils       //工具函数

3.主要技术点分析

  • 路由:
export default new Router({
  //mode: 'history',

  routes: [
    {
      path: '/',
      redirect: '/admin/index/carousel'
    }, {
      path: '/',
      component: resolve => require(['../components/common/home/home.vue'], resolve),
      children: [
        //首页
        {
          path: '/',
          component: resolve => require(['../components/pages/focus/Index.vue'], resolve)
        },
        ……
      ]
    }
  ]
})
  • 数据流:
    数据流
    • 父组件到子组件,在父组件调用子组件时通过v-bind(:)绑定动态数据,在子组件,使用Prop方法(单项绑定,防止数据倒流)

      父:
      <edit-dialog :dialogData="dialogData" :rendom="new Date().getTime()"></edit-dialog>

      子:

      props: {
        dialogData: Object,
        rendom: Number
      }
      

      子组件中通过watch方法来监控数据是否改变,可触发相应的方法。

    • 子组件到父组件,在父组件中使用v-on(@)绑定自定义事件接收,在子组件中使用$emit来监控,并传给回掉。
      父:
      <edit-dialog @message="recieveMessage"></edit-dialog>

      子:

      this.$emit('message', data)
      
    • 非父子组件,有时候两个组件也需要通信(非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为**事件总线:

      var bus = new Vue()
      
      // 触发组件 A 中的事件
      bus.$emit('id-selected', 1)
      
      // 在组件 B 创建的钩子中监听事件
      bus.$on('id-selected', function (id) {
        // ...
      })
      

小结

乱七八糟的写了很多,也算是对这段时间用vue.js的一个回顾。不得不承认,在使用vue.js的过程当中开始逐渐喜欢上了这个优美而简洁的框架。因此也愿意跟更多的人分享使用它的经验。也欢迎大家一起交流。

记Chrome移动端浏览器渲染问题

前一段时间在开发H5页面中遇到一个奇葩的问题,在Chrome浏览器中通过改变页面状态,在其他浏览器中测试是正常的,但是在Chrome中页面中按钮的背景色渲染不出来,先来看看问题到底长什么样:
问题重现
c

查了一下,后来发现这个是渲染引擎的 bug 导致的问题。后来在网上查了一下,给当前元素css添加了transform: translateZ(0);,使用 3D 加速将这个渲染层隔离渲染。果然完美解决了。还是比较简单的。
zx

js中的event.currenttarget 和 this的区别

前几天在使用ES6的时候发现箭头函数中,事件无法通过this获取本身,后来查了一些资料是这样的:

一般来说,thisevent.currentTarget是一致的。
但是,如果使用了某种作用域替换,(比如jquery.Proxy,或者 ES6CoffeeScript 等里用了 =>),this可能有别的含义了,用 event.currentTarget 就更安全。
一般人们更容易混淆的是 event.targetevent.currentTarget

VUE组件之间数据传递全集

父子组件之间传递

父 -> 子

(props)属性值方式

  • 父组件关键代码:

    <template>
      <Child :child-msg="msg"></Child>
    </template>
  • 子组件关键代码:

    export default {
      name: 'child',
      props: {
        child-msg: String //这里指定了字符串类型,如果类型不一致会警
      }
    };

child-msg 为父组件给子组件设置的额外属性值,属性值需在子组件中设置props,子组件中可直接使用child-msg变量。

子组件调用父组建

子组件通过 $parent 获得父组件,通过 $root 获得最上层的组件。

provider/inject

简单的来说,就是在父组件中通过provider提供变量,在子组件中通过inject来注入组件。不论(子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

实例:

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}  

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

子 -> 父

发送/监听事件

  • 子组件某函数中发送事件:

    this.$emit('eventName','data')
  • 父组件中监听子组件事件:

    <Child @eventName='todo()'></Child>
    • eventName为子组件自定义发送事件名称
    • todo()为父组件处理方法
    • data为子组件向父组建传递的参数

父组件直接获取子组建属性和方法

给要调用的子组件起个名字。将名字设置为子组件 ref 属性的值。

<!-- ref的值是组件引用的名称 -->
<child-component ref="name"></child-component>

父组件中通过$refs组件名来获得子组件,也就可以调用子组件的属性和方法了。

let child = this.$refs.name
child.属性
child.方法()

父组件通过 $children 可以获得所有直接子组件(父组件的子组件的子组件不是直接子组件)。需要注意 $children 并不保证顺序,也不是响应式的。

非父子组件

eventBus

目前eventBus通行方式是解决兄弟组件之间通信的最佳方式。使用方法:

  • 定义一个新的vue实例

    //eventBus.js
    import Vue from 'vue'
    export default new Vue();
  • 发送事件(发送数据)

    import bus from '@/bus';
    
    //方法内执行下面动作
    bus.$emit('childa-message', this.data);
  • 组件内监听(接收数据组件)

    import bus from '@/bus';
    
    //方法内执行下面动作
    bus.$on('childa-message', function(data) {
      console.log('I get it');
    });

vuex方式传值

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

ECMAScript6 编码规范

ECMAScript6 编码规范

本规范是基于JavaScript规范拟定的,只针对ES6相关内容进行约定

如变量命名,是否加分号等约定的请参考JavaScript规范

应注意目前的代码转换工具(如Babel,Traceur)不够完善,有些特性须谨慎使用

ES6 Coding Style English Version

规范内容

  1. 声明 Declarations
  2. 字符串 Strings
  3. 解构 Destructuring
  4. 数组 Arrays
  5. 函数 Functions
  6. 类 Classes
  7. 模块 Modules
  8. 版权 Copyright

声明

  • 1.1 变量

对于只在当前作用域下有效的变量,应使用let来代替var

对于全局变量声明,采用var,但应避免声明过多全局变量污染环境

// 不好
const variables;
const globalObj = null; // 不是常量
let globalObj = null;

for (var i = 0; i < 5; i++) {
  console.log(i);
}
console.log(i); // 4


// 好
let variables;
var globalObj = null;

for (let i = 0; i < 5; i++) {
  console.log(i);
}
console.log(i); // ReferenceError: i is not defined
  • 1.2 常量

对于常量应使用const进行声明,命名采用驼峰写法

对于使用 immutable 数据应用const进行声明

注意: constlet只在声明所在的块级作用域内有效

// 不好
let someNum = 123;
const AnotherStr = '不变的字符串';
let arr = ['不', '变', '数', '组'];
var ANOTHER_OBJ = {
  '不变对象': true
};


// 好
const someNum = 123;
const anotherStr = '不变的字符串';
const arr = ['不', '变', '数', '组'];
const anotherObj = {
  '不变对象': true
};

字符串

  • 2.1 处理多行字符串,使用模板字符串

以反引号( ` )标示

可读性更强,代码更易编写

注意排版引起空格的问题,使用场景为声明HTML模板字符串

// 不好
const tmpl = '<div class="content"> \n' +
              '<h1>这是换行了。</h1> \n' +
            '</div>';


// 好
const tmpl = `
<div class="content">
  <h1>这是换行了。</h1>
</div>`;
  • 2.2 处理字符串拼接变量时,使用模板字符串
  // 不好
  function sayHi(name) {
    return 'How are you, ' + name + '?';
  }


  // 好
  function sayHi(name) {
    return `How are you, ${name}?`;
  }

解构

  • 3.1 嵌套结构的对象层数不能超过3层
// 不好
let obj = {
  'one': [
    {
      'newTwo': [
        {
          'three': [
            'four': '太多层了,头晕晕'
          ]
        }
      ]
    }
  ]
};


// 好
let obj = {
  'one': [
    'two',
    {
      'twoObj': '结构清晰'
    }
  ]
};
  • 3.2 解构语句中统一不使用圆括号
// 不好
[(a)] = [11]; // a未定义
let { a: (b) } = {}; // 解析出错


// 好
let [a, b] = [11, 22];
  • 3.3 对象解构

对象解构 元素与顺序无关

对象指定默认值时仅对恒等于undefined ( !== null ) 的情况生效

  • 3.3.1 若函数形参为对象时,使用对象解构赋值
// 不好
function someFun(opt) {
  let opt1 = opt.opt1;
  let opt2 = opt.opt2;
  console.log(op1);
}


// 好
function someFun(opt) {
  let { opt1, opt2 } = opt;
  console.log(`$(opt1) 加上 $(opt2)`);
}

function someFun({ opt1, opt2 }) {
  console.log(opt1);
}
  • 3.3.2 若函数有多个返回值时,使用对象解构,不使用数组解构,避免添加顺序的问题
// 不好
function anotherFun() {
  const one = 1, two = 2, three = 3;
  return [one, two, three];
}
const [one, three, two] = anotherFun(); // 顺序乱了
// one = 1, two = 3, three = 2


// 好
function anotherFun() {
  const one = 1, two = 2, three = 3;
  return { one, two, three };
}
const { one, three, two } = anotherFun(); // 不用管顺序
// one = 1, two = 2, three = 3
  • 3.3.3 已声明的变量不能用于解构赋值(语法错误)
// 语法错误
let a;
{ a } = { b: 123};
  • 3.4 数组解构

数组元素与顺序相关

  • 3.4.1 交换变量的值
let x = 1;
let y = 2;

// 不好
let temp;
temp = x;
x = y;
y = temp;


// 好
[x, y] = [y, x]; // 交换变量
  • 3.4.2 将数组成员赋值给变量时,使用数组解构
const arr = [1, 2, 3, 4, 5];

// 不好
const one = arr[0];
const two = arr[1];


// 好
const [one, two] = arr;

数组

  • 4.1 将类数组(array-like)对象与可遍历对象(如Set, Map)转为真正数组

采用Array.from进行转换

// 不好
function foo() {
  let args = Array.prototype.slice.call(arguments);
}


// 好
function foo() {
  let args = Array.from(arguments);
}
  • 4.2 数组去重

结合Set结构与Array.from

使用indexOf,HashTable等形式,不够简洁清晰

// 好
function deduplication(arr) {
  return Array.from(new Set(arr));
}
  • 4.3 数组拷贝

采用数组扩展...形式

const items = [1, 2, 3];

// 不好
const len = items.length;
let copyTemp = [];
for (let i = 0; i < len; i++) {
  copyTemp[i] = items[i];
}


// 好
let copyTemp = [...items];
  • 4.4 将一组数值转为数组

采用Array.of进行转换

// 不好
let arr1 = new Array(2); // [undefined x 2]
let arr2 = new Array(1, 2, 3); // [1, 2, 3]


// 好
let arr1 = Array.of(2);  // [2]
let arr2 = Array.of(1, 2, 3); // [1, 2, 3]

函数

  • 5.1 当要用函数表达式或匿名函数时,使用箭头函数(Arrow Functions)

箭头函数更加简洁,并且绑定了this

// 不好
const foo = function(x) {
  console.log(foo.name); // 返回'' ,函数表达式默认省略name属性
};

[1, 2, 3].map(function(x) {
  return x + 1;
});

var testObj = {
  name: 'testObj',
  init() {
    var _this = this; // 保存定义时的this引用
    document.addEventListener('click', function() {
      return _this.doSth();
    }, false);
  },
  doSth() {
    console.log(this.name);
  }
};

// 好
const foo = (x) => {
  console.log(foo.name); // 返回'foo'
};

[1, 2, 3].map( (x) => {
  return x + 1;
});

var testObj = {
  name: 'testObj',
  init() {
    // 箭头函数自动绑定定义时所在的对象
    document.addEventListener('click', () => this.doSth(), false);
  },
  doSth() {
    console.log(this.name);
  }
};
  • 5.1.1 箭头函数书写约定

函数体只有单行语句时,允许写在同一行并去除花括号

当函数只有一个参数时,允许去除参数外层的括号

// 好
const foo = x => x + x; // 注意此处会隐性return x + x

const foo = (x) => {
  return x + x; // 若函数体有花括号语句块时须进行显性的return
}; 

[1, 2, 3].map( x => x * x);
  • 5.1.2 用箭头函数返回一个对象,应用括号包裹
// 不好
let test = x => { x: x }; // 花括号会变成语句块,不表示对象


// 好
let test = x => ({ x: x }); // 使用括号可正确return {x:x}
  • 5.2 立即调用函数 IIFE

使用箭头函数

// 不好
(function() {
  console.log('哈');
})();


// 好
(() => {
  console.log('哈');
})();
  • 5.3 不使用 arguments, 采用rest语法...代替

rest参数是真正的数组,不需要再转换

注意:箭头函数中不能使用arguments对象

// 不好
function foo() {
  let args = Array.prototype.slice.call(arguments);
  return args.join('');
}


// 好
function foo(...args) {
  return args.join('');
}
  • 5.4 函数参数指定默认值

采用函数默认参数赋值语法

// 不好
function foo(opts) {
  opts = opts || {};// 此处有将0,''等假值转换掉为默认值的副作用
}


// 好
function foo(opts = {}) {
  console.log('更加简洁,安全');
}
  • 5.5 对象中的函数方法使用缩写形式

更加简洁

函数方法不要使用箭头函数,避免this指向的混乱

// 不好
const shopObj = {
  des: '对象模块写法',
  foo: function() {
    console.log(this.des);
  }
};

const shopObj = {
  des: '对象模块写法',
  foo: () => {
    console.log(this.des); // 此处会变成undefined.des,因为指向顶层模块的this
  }
};

// 好
const des = '对象模块写法'; // 使用对象属性值简写方式
const shopObj = {
  des,
  foo() {
    console.log(this.des);
  }
};

  • 6.1 类名应使用帕斯卡写法(PascalCased)
// 好
class SomeClass {

}
  • 6.1.1 类名与花括号须保留一个空格间距

类中的方法定义时, 方法名与左括号 ( 之间【不保留】空格间距

类中的方法定义时,右括号 ) 须与花括号 { 【保留】一个空格间距

// 不好
class Foo{
  constructor(){
    // 右括号 `)` 须与花括号 `{` 仅保留一个空格间距
  }
  sayHi()    {

  }
  _say () {
    // 方法名与左括号 `(` 之间【不保留】空格间距
  }

}


// 好
class Foo {
  constructor() {
    // 右括号 `)` 须与花括号 `{` 仅保留一个空格间距
  }
  sayHi() {

  }
  _say() {
    // 方法名与左括号 `(` 之间【不保留】空格间距
  }
}
  • 6.2 定义类时,方法的顺序如下:
    • constructor
    • public get/set 公用访问器,set只能传一个参数
    • public methods 公用方法,公用相关命名使用小驼峰式写法(lowerCamelCase)
    • private get/set 私有访问器,私有相关命名应加上下划线 _ 为前缀
    • private methods 私有方法
// 好
class SomeClass {
  constructor() {
    // constructor
  }

  get aval() {
    // public getter
  }

  set aval(val) {
    // public setter
  }

  doSth() {
    // 公用方法
  }

  get _aval() {
    // private getter
  }

  set _aval() {
    // private setter
  }

  _doSth() {
    // 私有方法
  }
}
  • 6.3 如果不是class类,不使用new
// 不好
function Foo() {

}
const foo = new Foo();


// 好
class Foo {

}
const foo = new Foo();
  • 6.4 使用真正意思上的类Class写法,不使用prototype进行模拟扩展

Class更加简洁,易维护

// 不好
function Dog(names = []) {
  this._names = [...names];
}
Dog.prototype.bark = function() {
  const currName = this._names[0];
  alert(`one one ${currName}`);
}

// 好
class Dog {
  constructor(names = []) {
    this._names = [...names];
  }
  bark() {
    const currName = this._names[0];
    alert(`one one ${currName}`);
  }
}
  • 6.5 class应先定义后使用

class不存在hoist问题,应先定义class再实例化

使用继承时,应先定义父类再定义子类

// 不好
let foo = new Foo();
class SubFoo extends Foo {

}
class Foo {

}


// 好
class Foo {

}
let foo = new Foo();
class SubFoo extends Foo {

}
  • 6.6 this的注意事项

子类使用super关键字时,this应在调用super之后才能使用

可在方法中return this来实现链式调用写法

class Foo {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

// 不好
class SubFoo extends Foo {
  constructor(x, y, z) {
    this.z = z; // 引用错误
    super(x, y);
  }
}


// 好
class SubFoo extends Foo {
  constructor(x, y, z) {
    super(x, y);
    this.z = z; // this 放在 super 后调用
  }
  setHeight(height) {
    this.height = height;
    return this;
  }
}

模块

  • 7.1 使用import / export来做模块加载导出,不使用非标准模块写法

跟着标准走的人,运气总不会太差

// 不好
const colors  = require('./colors');

module.exports = color.lightRed;


// 好
import { lightRed } from './colors';
export default lightRed;
  • 7.1.1 import / export 后面采用花括号{ }引入模块的写法时,须在花括号内左右各保留一个空格
// 不好
import {lightRed} from './colors';
import { lightRed} from './colors';

// 好
import { lightRed } from './colors';
  • 7.2 应确保每个module有且只有一个默认导出模块

方便调用方使用

// 不好
const lightRed = '#F07';
export lightRed;


// 好
const lightRed = '#F07';
export default lightRed;
  • 7.3 import 不使用统配符 * 进行整体导入

确保模块与模块之间的关系比较清晰

// 不好
import * as colors from './colors';

// 好
import colors from './colors';
  • 7.4 不要将importexport混合在一行

分开导入与导出,让结构更清晰,可读性更强

// 不好
export { lightRed as default } from './colors';

// 好
import { lightRed } from './colors';
export default lightRed;
  • 7.5 多变量要导出时应采用对象解构形式

export置于底部,使欲导出变量更加清晰

// 不好
export const lightRed = '#F07';
export const black  = '#000';
export const white  = '#FFF';

// 好
const lightRed = '#F07';
const black  = '#000';
const white  = '#FFF';

export default { lightRed, black, white };

Copyright

Copyright (c) 2016 Hancoson

Node.js 之前端请求转发

好几年之前【大前端】这个词语就开始在“dev er”中流行起来了,那么大前端到底包含了哪些技术呢?传统的FE、Native(Hybrid)、Node、图形技术、VR……,今天我们来着重说说其中简单的一块——Nodejs(请求转发)。

node

需求

明确用 Node 来干什么,很重要。

  • 从后台读取对应的 API
  • 处理读取的数据,并发给前端(自己)

很明显这样可以完全抛弃 JSP 语言,并由前端自己来完成。

开发

说完就撸起袖子干吧~~~,下文以「blog中的demo(已开源)」为例,引入 Express 框架。

目录机构

.
├── app.js //入口文件
├── config //配置文件
├── controllers //控制器
├── logs //日志
├── models  //模型
├── node_modules //依赖
├── package.json
├── public //静态资源
├── routes //路由
├── services //服务
├── utils //工具方法
├── views //模版
   └── index.html
└── yarn.lock

从目录机构开看是比较简单的,好了,我们来详细介绍一下比较主要的功能吧。

创建入口文件

进入工程目录,新建文件 app.js ,输入如下:

var express = require('express')
var http = require('http')
var path = require('path')
var ejs = require('ejs')
var router = require('./routes/index')
var app = express();
router(app); //拆分路由
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public'))); //静态文件服务位置
app.engine('.html', ejs.__express);//使用.html做为后缀
app.set('view engine', 'html'); //使用ejs为模版引擎

http.createServer(app).listen(app.get('port'), function () {
  console.log('Express server listening on port http://localhost:' + app.get('port'));
});

拆分路由

新建文件 /router.js

var index = require('./../controllers/index')
module.exports = (app) => {
  app.get('/', index.index);
  app.get('/api/get', index.get);
}

以后,再添加其他的任何路由,只要修改 router.js 就是了。

拆分模型

模型 model 专门处理数据,无论是数据库,还是请求远程 api 资源,都应该是它的事。自然,我们可以把请求独立出来,这么做:

新建文件夹和文件 /models/index.js ,剪切粘贴下面的代码:

var myFetch = require('./../utils/fetch')
var config = require('./../config/config')
var errCode = require('./../config/errCode')
var Index = {
  //get
  get: function (req, callback) {
    //这里使用了知乎日报API
    myFetch('https://news-at.zhihu.com/api/4/news/before/' + req.t, {}, function (err, data) {
      if (!err) {
        callback(null, data)
      } else {
        callback(null, errCode.SERVER_ERR)
      }
    })
  }
}
module.exports = Index

拆分控制器

控制器负责从模型请求数据,并把数据发送到前端,是前端和后台的调度员。这里,app.get 方法里的匿名函数便是,我们分别把他们抽取出来,放在 /controllers/index.js 里,并把请求 Api 的代码用模型代替,代码如下:

var Indexs = require('./../models/index')
var Index = {
  //get '/'
  index: function (req, res, next) {
    res.render('index', { title: 'Express' });
  },
  //api/get
  get: function (req, res) {
    Indexs.get(req.query, function (err, data) {
      console.log(req.query, err, data)
      if (err) {
        res.json({ status: 500, msg: err })
      } else {
        res.json(data)
      }
    })
  }
}
module.exports = Index

说明:按照惯例,控制器的名称,通常是对应模型的名称(名词)的复数。

经过这样的模块化整理,我们轻松实现了一个简单的MVC框架,它的易用性、扩展性得到很大提升。我们已经可以快速添加更多的功能了。

总结

本文涉及的代码还是非常简单的,更多的高大上功能还需要自己去折腾吧。

本文的所有代码均来自我的 BOLG ,欢迎 STAR STAR STAR。传送门>>

Git常用命令备忘

Git常用命令备忘

Git配置

git config --global user.name "robbin"   
git config --global user.email "[email protected]"
git config --global color.ui true
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.br branch
git config --global core.editor "mate -w"    # 设置Editor使用textmate
git config -l  # 列举所有配置

用户的git配置文件~/.gitconfig

Git常用命令

查看、添加、提交、删除、找回,重置修改文件

git help <command>  # 显示command的help
git show            # 显示某次提交的内容
git show $id

git co  -- <file>   # 抛弃工作区修改
git co  .           # 抛弃工作区修改

git add <file>      # 将工作文件修改提交到本地暂存区
git add .           # 将所有修改过的工作文件提交暂存区

git rm <file>       # 从版本库中删除文件
git rm <file> --cached  # 从版本库中删除文件,但不删除文件

git reset <file>    # 从暂存区恢复到工作文件
git reset -- .      # 从暂存区恢复到工作文件
git reset --hard    # 恢复最近一次提交过的状态,即放弃上次提交后的所有本次修改

git ci <file>
git ci .
git ci -a           # 将git add, git rm和git ci等操作都合并在一起做
git ci -am "some comments"
git ci --amend      # 修改最后一次提交记录

git revert <$id>    # 恢复某次提交的状态,恢复动作本身也创建了一次提交对象
git revert HEAD     # 恢复最后一次提交的状态

查看文件diff

git diff <file>     # 比较当前文件和暂存区文件差异
git diff
git diff <$id1> <$id2>   # 比较两次提交之间的差异
git diff <branch1>..<branch2> # 在两个分支之间比较 
git diff --staged   # 比较暂存区和版本库差异
git diff --cached   # 比较暂存区和版本库差异
git diff --stat     # 仅仅比较统计信息

查看提交记录

git log
git log <file>      # 查看该文件每次提交记录
git log -p <file>   # 查看每次详细修改内容的diff
git log -p -2       # 查看最近两次详细修改内容的diff
git log --stat      # 查看提交统计信息

tig

Mac上可以使用tig代替diff和log,brew install tig

Git 本地分支管理

查看、切换、创建和删除分支

git br -r           # 查看远程分支
git br <new_branch> # 创建新的分支
git br -v           # 查看各个分支最后提交信息
git br --merged     # 查看已经被合并到当前分支的分支
git br --no-merged  # 查看尚未被合并到当前分支的分支

git co <branch>     # 切换到某个分支
git co -b <new_branch> # 创建新的分支,并且切换过去
git co -b <new_branch> <branch>  # 基于branch创建新的new_branch

git co $id          # 把某次历史提交记录checkout出来,但无分支信息,切换到其他分支会自动删除
git co $id -b <new_branch>  # 把某次历史提交记录checkout出来,创建成一个分支

git br -d <branch>  # 删除某个分支
git br -D <branch>  # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)

分支合并和rebase

git merge <branch>               # 将branch分支合并到当前分支
git merge origin/master --no-ff  # 不要Fast-Foward合并,这样可以生成merge提交

git rebase master <branch>       # 将master rebase到branch,相当于:
git co <branch> && git rebase master && git co master && git merge <branch>
Git补丁管理(方便在多台机器上开发同步时用)
git diff > ../sync.patch         # 生成补丁
git apply ../sync.patch          # 打补丁
git apply --check ../sync.patch  # 测试补丁能否成功

Git暂存管理

git stash                        # 暂存
git stash list                   # 列所有stash
git stash apply                  # 恢复暂存的内容
git stash drop                   # 删除暂存区

Git远程分支管理

git pull                         # 抓取远程仓库所有分支更新并合并到本地
git pull --no-ff                 # 抓取远程仓库所有分支更新并合并到本地,不要快进合并
git fetch origin                 # 抓取远程仓库更新
git merge origin/master          # 将远程主分支合并到本地当前分支
git co --track origin/branch     # 跟踪某个远程分支创建相应的本地分支
git co -b <local_branch> origin/<remote_branch>  # 基于远程分支创建本地分支,功能同上

git push                         # push所有分支
git push origin master           # 将本地主分支推到远程主分支
git push -u origin master        # 将本地主分支推到远程(如无远程主分支则创建,用于初始化远程仓库)
git push origin <local_branch>   # 创建远程分支, origin是远程仓库名
git push origin <local_branch>:<remote_branch>  # 创建远程分支
git push origin :<remote_branch>  #先删除本地分支(git br -d <branch>),然后再push删除远程分支
```js
### Git远程仓库管理
```js
git remote -v                    # 查看远程服务器地址和仓库名称
git remote show origin           # 查看远程服务器仓库状态
git remote add origin git@github:robbin/robbin_site.git         # 添加远程仓库地址
git remote set-url origin [email protected]:robbin/robbin_site.git # 设置远程仓库地址(用于修改远程仓库地址)
git remote rm <repository>       # 删除远程仓库

创建远程仓库

git clone --bare robbin_site robbin_site.git  # 用带版本的项目创建纯版本仓库
scp -r my_project.git git@git.csdn.net:~      # 将纯仓库上传到服务器上

mkdir robbin_site.git && cd robbin_site.git && git --bare init # 在服务器创建纯仓库
git remote add origin git@github.com:robbin/robbin_site.git    # 设置远程仓库地址
git push -u origin master                                      # 客户端首次提交
git push -u origin develop  # 首次将本地develop分支提交到远程develop分支,并且track

git remote set-head origin master   # 设置远程仓库的HEAD指向master分支

也可以命令设置跟踪远程库和本地库

git branch --set-upstream master origin/master
git branch --set-upstream develop origin/develop

Vue Node服务端渲染之Nuxt

Nuxt.js 简单介绍

Nuxt.js 是一个基于 Vue.js 的服务端渲染框架。

Vue SSR 的原理图

原理图

Nuxt.js的优势

  • 无需为了路由划分而烦恼,你只需要按照对应的文件夹层级创建 .vue 文件就行
  • 无需考虑数据传输问题,Nuxt 会在模板输出之前异步请求数据(需要引入 axios 库),而且对 vuex 有进一步的封装
  • 内置了 webpack,省去了配置 webpack 的步骤,nuxt 会根据配置打包对应的文件

下图阐述了 Nuxt.js 应用一个完整的服务器请求到渲染(或用户通过 <nuxt-link> 切换路由渲染页面)的流程:
流程

  • Incoming Request:浏览器发出一个请求

  • nuxtServerInit:服务端接收到请求,检查当前有没有nuxtServerInit这个配置项,如果有的化就先执行这个方法,这个方法是用来操作vuex的

  • middleware:这是一个中间件,跟路由相关,这里可以做任何想要的功能

  • validate():验证,配合高级动态路由去做一些验证,比如A页面是否允许跳转到B页面去,如果没有验证通过可以跳转到别的页面之类的校验

  • asyncData()&fetch():获取数据。asyncData()获取的数据是用来渲染vue组件的,fetch通常用来修改vuex的数据

  • Render:渲染

  • Naviage:如果发起了一个非Server 的路由,那么在执行一遍middleware——Render的过程

主要说明

asyncData

asyncData方法是 Nuxt.js 对 Vue 扩展的方法。 会在组件(限于页面组件)每次初始化前被调用的。所以该方法没有办法通过this来引用组件的实例对象。它可以在服务端或路由更新之前被调用。可以利用asyncData方法来获取数据并返回给当前组件。

asyncData (context) {
  return { project: 'nuxt' }
}

asyncData 的参数(context)有哪些?

  • app:app 对象
  • store:存储,可以拿到store里的数据
  • route:路由,可以拿到参数之类的
  • params:url 路径参数,主要获取id
  • query:可以理解为url上问号后面的参数
  • env:运行环境
  • isDev:是否是开发环境
  • isHMR:是否是热更新
  • redirect:重定向
  • error:错误

asyncData 可以做什么?

创建渲染器

Nuxt renderer 使用vue-server-renderer插件创建渲染器并解析 webpack 打好的 bundle 文件

fetch

fetch方法用于渲染页面前填充应用的状态树(store)数据,与asyncData方法类似,不同的是它不会设置组件的数据。

async fetch({ store, params }) {
  const { data } = axios.get('http://abc.com/api/stara')
  store.commit('setStars/set', data)
}

fetch或者asyncData这2个方法在layouts和components中是失效的。

server.js做了哪些事情

每个用户通过浏览器访问 Vue 页面时,都是一个全新的上下文,但在服务端,应用启动后就一直运行着,处理每个用户请求的都是在同一个应用上下文中。为了不串数据,需要为每次 SSR 请求,创建全新的 app, store, router。

我们主要关注最后一部分asyncData部分

首先会根据上下文环境中的url调用 将匹配的component返回

接着遍历每个component,根据component的asyncData配置,执行 promisify()来promise化asyncData方法并将上下文对象赋给asyncData方法

promisify()方法接受两个参数:第一个组件中配置的asyncData()方法;第二个是挂载到新vue实例上的上下文对象

执行后通过方法将得到的数据同步一份给页面中定义的data,asyncData只是在首屏的时候调用一次,后续交互还是交给client处理

window和document对象的使用

在开发nuxt项目的时候,我们难免会使用到window或者document来获取dom元素。如果直接在文件中使用就会报错。这是因为window或者document是浏览器端的东西服务端并没有。
解决方法:我们只需要在使用的地方通过process.browser/process.server来判断

if (process.browser) {
   // 浏览器中运行代码
}

nuxtServerInit

如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。

举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}

性能优化

keep-alive

nuxt1.2.0 上添加了这个功能的。在layouts/default.vue:

<template>
  <Nuxt keep-alive/>
</template>

服务端渲染合理应用

应用到的特性主要包括asyncData异步获取数据、mounted不支持服务端渲染、client-only组件不在服务端渲染中呈现;

通过相关特性做到API数据和页面结构合理拆分,首屏所需数据和结构通过服务端获取并渲染,非首屏数据和结构通过客户端获取并渲染。

<template>
  <div>
    <!-- 顶部banner -->
    <banner :banner="banner" />
    <!-- 非首屏所需结构,通过client-only组件达到不在服务端渲染目的-->
    <client-only>
      <!-- 列表 -->
      <prod-list :listData="listData"/>
    </client-only>
  </div>
</template>

组件缓存

将渲染后的组件DOM结构存入缓存,定时刷新,有效期通过缓存获取组件DOM结构,减小生成DOM结构所需时间;

适用于渲染后结构不变或只有几种变换、并不影响上下文的组件。

nuxt.config.js配置项修改

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: new LRUCache({
        max: 1000, // 缓存队列长度
        maxAge: 1000 * 60 // 缓存1分钟
      })
    }
  }
}

需要做缓存的 vue 组件, 需增加name以及serverCacheKey字段,以确定缓存的唯一键值。

export default {
  name: 'componentName',
  props: ['item'],
  serverCacheKey: props => props.item.id
}
  • 如果组件依赖于很多的全局状态,或者状态取值非常多,缓存会因频繁被设置而导致溢出,这样的组件做缓存就没有多大意义了;

  • 组件缓存,只是缓存了dom结构,如created等钩子中的代码逻辑并不会被缓存,如果其中逻辑会影响上下边变更,是不会再执行的,此种组件也不适合缓存。

页面整体缓存

当整个页面与用户数据无关,依赖的数据基本不变的情况下,可以对整个页面做缓存,减小页面获取时间;

页面整体缓存前提是在使用Nuxt.js脚手架工具create-nuxt-app初始化项目时,必须选择集成服务器框架,如expresskoa,只有这样才具有服务端中间件扩展的功能。

页面缓存

对于使用nuxt框架而言,用脚手架初始化完,框架对于vue服务端渲染的res.end()函数做了高度封装,
服务端中间件middleware/page-cache.js

import LRUCache from "lru-cache";
const cache = new LRUCache({
  maxAge: 1000 * 60 * 2, // 有效期2分钟
  max: 100 // 最大缓存数量
});

export default function(req, res, next) {
  // 本地开发环境不做页面缓存
  if (process.env.NODE_ENV !== "development") {
    try {
      const cacheKey = req.url;
      const cacheData = cache.get(cacheKey);
      if (cacheData) {
        return res.end(cacheData, "utf8");
      }
      const originalEnd = res.end;
      res.end = function(data) {
        cache.set(cacheKey, data);
        originalEnd.call(res, ...arguments);
        console.log(data);
      };
    } catch (error) {
      console.log(`page-cache-middleware: ${error}`);
      next();
    }
  }
  next();
}

nuxt.config.js配置项修改,引入服务端中间件

serverMiddleware:[
  { path: '/app', handler: '~/middleware/page-cache' }
]

服务器端渲染中间件(serverMiddleware) vs 中间件(middleware)。不要将它与客户端或SSR中Vue在每条路由之前调用的routes middleware混淆。serverMiddleware只是在vue-server-renderer之前在服务器端运行,可用于服务器特定的任务,如处理API请求或服务资产。

问题

服务端的 axios 不会自动携带 Cookie

在 axios 封装那里新增一个接口来注入 Cookie,然后靠 router-middleware 从 context.headers.cookie 获取并注入

资料

JS 常见算法积累(一)

算法技能如今越来越被看重,今天开始把平时记录的一些关于JS算法的题目做一下记录,弥补自己在这一项的缺陷。

数组去重

  • 先来说说比较�容易想到的方法:
function unique(arr){
  var newArr = [];
  for(var i = 0;i<arr.length;i++){
    if(newArr.indexOf(arr[i]) === -1){
      newArr.push(arr[i])
    }
  }
  return newArr
}

该方中的indexOf兼容性需要考虑;

  • 利用Object中key的唯一性,利用key来进行筛选:
function unique(arr){
  var obj = {}
  var data = []
    for(var i in arr){
      if(!obj[arr[i]]){
        obj[arr[i]] = true;
        data.push(arr[i]);
      }
    }
  return data;
}
  • 排序后去重:
function unique(arr){
  var newArr = [];
  arr.sort();
	for(var i=0;i<arr.length;i++){
    if(i===0){
      newArr.push(arr[i])
    }
    else{
      if(arr[i]!==arr[i-1]){
        newArr.push(arr[i])
      }
    }
  }
  return newArr;
}

该方法会打乱原有数组的顺序;

对象根据属性排序

  • arrayObject.sort(sortby)sortby非必填但必须为一个函数,规定排序顺序。这里嵌套一层函数用来接收对象属性名,其他部分代码与正常使用sort方法相同:

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

  • 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
  • 若 a 等于 b,则返回 0。
  • 若 a 大于 b,则返回一个大于 0 的值。
var arr = [
  {name:'zopp',age:0},
  {name:'gpp',age:18},
  {name:'yjj',age:8}
];

function compare(property){
  return function(a,b){
    var value1 = a[property];
    var value2 = b[property];
    return value1 - value2;
  }
}
console.log(arr.sort(compare('age')))

取数组中最大差值

  • 该方法很容易想到,就是�找到最大的值减去最小的值即可得到:
function getMax(arr){
  var min = arr[0];
  var max = arr[0];  //先假定第一个值及时最大也是最小的
  for(var i = 0;i<arr.length;i++){
    if(arr[i]<min) min = arr[i]

    if(arr[i]>max) max = arr[i]
  }
  return max - min
}

排序

�动画演示

冒泡排序

  • 比较简单的排序算法,说白了就是把相邻的两个元素拿来比较大小并重复进行,如果后面的比前面的小,把�小的放在前面。
  • 时间复杂度:O(n2)
  • 具体算法描述如下:
    • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
    • 针对所有的元素重复以上的步骤,除了最后一个;
    • 重复步骤1~3,直到排序完成。
function bubbleSort(arr){
  for(var i = 0;i<arr.length-1;i++){
    for(var j = 0;j<arr-1;j++){
      if(arr[j+1]<arr[j]){
        var temp = arr[j+1];
        arr[j+1] = arr[j];
        arr[j] = temp
      }
    }
  }
  return arr;
}

选择排序

  • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
  • 时间复杂度:O(n2)
function findMin(arr,first){
  var minIndex = first; //定义最小下标
  var minNum = arr[first]; //定义数组中的最小值
  for(var i=first+1;i<arr.length;i++){
    if(arr[i]<minNum){
      minIndex = i;
      minNum = arr[i]
    }
  }
  return minIndex; //返回最小的下标
}

function selectionSort(arr){
  for(var i = 0;i<arr.length;i++){
    var min = findMin(arr,i);
    var temp = arr[min];
    arr[min] = arr[i];
    arr[i] = temp;
  }
  return arr;
}

快速排序

  • 在数据集之中,选择一个元素作为"基准"(pivot)
  • 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边
  • 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止
function quickSort(arr){
  if(arr.length <=1){
    return arr;
  }
  else{
    var pivotIndex = Math.floor(arr.length/2); //找基准值,一般取中间项
    var pivot = arr.splice(pivotIndex,1)[0]; //删除原来的基准值
    var left = [],right = [];
    for(var i = 0;i<arr.length;i++){
      if(arr[i]<=pivot) {
        left.push(arr[i])
      }
      else{
        right.push(arr[i])
      }
    }
  }
  return quickSort(left).concat([pivot],quickSort(right)); //返回左边数组+基准值+右边数组
}

Flutter 之 环境配置

环境要求

  • 软件:Xcode、Android studio、VScode
  • 工具: Flutter 依赖下面这些命令行工具:
    • bash, mkdir, rm, git, curl, unzip, which
    • brew 是 Mac 下的一个包管理工具,类似于 centos 下的 yum,可以很方便地进行安装/卸载/更新各种软件包

下载安装Flutter SDK

下载

两种方式:官网下载和github下载

  • flutter官网下载地址点击进入
  • 在国内因为中所周知的原因,要想正常获取安装包列表或下载安装包,可能需要翻墙,大家也可以去Flutter github项目下去下载安装包,转到下载页

安装

  • 解压安装包到你想安装的目录,如:
cd ~/development
unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip
  • 添加flutter相关工具到path中(只能暂时针对当前命令行窗口设置PATH环境变量):
export PATH=`pwd`/flutter/bin:$PATH

说明:作为国内用户且需要将Flutter添加到PATH中请参考下面

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
export PATH=$PATH:/Library/flutter/bin  #$PATH为你解压的flutter文件路径,我放在了根目录
  • 其他还需要安装Android sdk
export PATH=$PATH:/Library/Android/sdk

检查开发环境

到此我们已经安装完flutter了,但此时还不具备开发的能力,flutter运行需要很多插件,运行以下命令查看是否需要安装其它依赖项来完成安装:

flutter doctor

这时候它会将你未安装的依赖一一列出,每个电脑缺少的依赖都不尽相同,按照给出的提示安装插件即可。直到你的开发工具通过验证即可,如:
WX20190708-110814@2x

至此,全部环境搭建步骤结束。

项目创建

经过上面三部环境基本上差不多了,现在来建立一个Flutter项目。
进入你想存放项目的目录,执行命令行:

flutter create flutterapp

其中flutterapp为项目名称,不能用大写。

运行

Android studio

  • 下载AndroidStudio 并安装插件 Flutter Dart
  • 通过 Android Stadio 的 File => New => New Flutter Project 去新建项目demo
  • 下载SDK 并 执行demo

VScode

  • 安装插件 Flutter Dart
  • /cmd + /shirft + p => flutter new project
  • 打开调试工具 并执行flutter run

React.createClass vs extends Component


当今 React 比较流行,可能会有很多的新手和我一样遇到这个问题:

var MyClass = React.createClass({...});class MyClass extends React.Component{...}

之间的区别是什么?那么今天带大家一起学习一下。

写法的区别

React 中有两种支持创建组件的方法,你可以通过 React.createClassextends React.Component,他们实现的目的都是一样的。区别就在于你是否是以 ES6 的形式。

当你使用 ES6,你应该在 constructor 中初始化你的参数:

class MyComponent extends React.Component {
    constructor(props) {
    super(props);
    this.state = { /* initial state, this is ES6 syntax (classes) */ };
  }
}

当你使用 React.createClass 你需要使用 getInitialState

var MyComponent = React.createClass({
  getInitialState() {
    return { /* initial state */ };
  },
});
  • 这两种写法的方法名首字母都必须大写

函数 this 自绑定

React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,任何时候使用,直接使用this.method即可,函数中的this会被正确设置。

const Contacts = React.createClass({
  handleClick() {
    console.log(this); // React Component instance
  },
  render() {
    return (
      <div onClick={this.handleClick}></div>
    );
  }
});

React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。

class Contacts extends React.Component {
  constructor(props) {
    super(props);
  }
  handleClick() {
    console.log(this);
  }
  render() {
    return (
      <div onClick={this.handleClick.bind(this)}></div>
    );
  }
}

当然,React.Component有三种手动绑定方法:可以在构造函数中完成绑定,也可以在调用时使用method.bind(this)来完成绑定,还可以使用arrow function来绑定。拿上例的handleClick函数来说,其绑定可以有

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this); //构造函数中绑定
}
<div onClick={this.handleClick.bind(this)}></div> //使用bind来绑定
<div onClick={()=>this.handleClick()}></div> //使用arrow function来绑定

组件属性类型propTypes及其默认props属性defaultProps配置不同

React.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的:

const TodoItem = React.createClass({
  propTypes: { // as an object
    name: React.PropTypes.string
  },
  getDefaultProps(){   // return a object
    return {
      name: ''
    }
  }
  render(){
    return <div></div>
  }
})

React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。对应上面配置如下:

class TodoItem extends React.Component {
  static propTypes = {//类的静态属性
    name: React.PropTypes.string
  };

  static defaultProps = {//类的静态属性
    name: ''
  };

  //...
}

组件初始状态state的配置不同

React.createClass创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态;
React.Component创建的组件,其状态state是在constructor中像初始化组件属性一样声明的。

const TodoItem = React.createClass({
  // return an object
  getInitialState(){
    return {
      isEditing: false
    }
  }
  render(){
    return <div></div>
  }
})
class TodoItem extends React.Component{
  constructor(props){
    super(props);
    this.state = { // define this.state in constructor
      isEditing: false
    }
  }
  render(){
    return <div></div>
  }
}

Mixins的支持不同

Mixins(混入)是面向对象编程OOP的一种实现,其作用是为了复用共有的代码,将共有的代码通过抽取为一个对象,然后通过Mixins进该对象来达到代码复用。

React.createClass在创建组件时可以使用mixins属性,以数组的形式来混合类的集合。

var SomeMixin = {
  doSomething() {

  }
};
const Contacts = React.createClass({
  mixins: [SomeMixin],
  handleClick() {
    this.doSomething(); // use mixin
  },
  render() {
    return (
      <div onClick={this.handleClick}></div>
    );
  }
});

但是遗憾的是React.Component这种形式并不支持Mixins,至今React团队还没有给出一个该形式下的官方解决方案;但是React开发者社区提供一个全新的方式来取代Mixins,那就是Higher-Order Components(高阶组件)。

如何做好 Git Commit message 规范化

如今 Git 在团队开发中使用已经越来越普遍,那么如何做好提交信息的规范化呢?本文主要结合 Angular Git Commit message 规范,来介绍一下相关的配套工具使用。

Commit message 规范化的好处

  • 提供更多的历史信息,方便快速浏览。
  • 可以过滤某些commit(比如文档改动),便于快速查找信息。

如何规范化 Commit message

下面主要介绍一下相关的工具。

Commitizen

commitizen是一个撰写合格 Commit message 的工具。

安装如下:

$ npm i -g commitizen

然后,在项目目录里,运行下面的命令,使其支持 Angular 的 Commit message 格式。

$ commitizen init cz-conventional-changelog --save --save-exact

以后,凡是用到git commit命令,一律改为使用git cz。这时,就会出现选项,用来生成符合格式的 Commit message。

add-commit.png

  • feat:新增功能;
  • fix:修复bug;
  • docs:修改文档;
  • refactor:代码重构,未新增任何功能和修复任何bug;
  • build:改变构建流程,新增依赖库、工具等(例如- webpack修改);
  • style:仅仅修改了空格、缩进等,不改变代码逻辑;
  • perf:改善性能的修改;
  • chore:非src和test的修改;
  • test:测试用例的修改;
  • ci:自动化流程配置修改;
  • revert:回滚到上一个版本;

生成 Change log

如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成
例子
例子

生成的文档包括以下三个部分。

  • New features
  • Bug fixes
  • Breaking changes.

每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。
conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。

$ npm install -g conventional-changelog-cli
$ cd my-project
$ conventional-changelog -p angular -i CHANGELOG.md -s

上面命令不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动。

如果你想生成所有发布的 Change log,要改为运行下面的命令:

$ conventional-changelog -p angular -i CHANGELOG.md -s -r 0

为了方便使用,可以将其写入package.json的scripts字段。

{
  "scripts": {
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
  }
}

以后,直接运行下面的命令即可。

$ npm run changelog

原文地址:http://vsoui.com/2018/09/29/git-commit-message/

探讨团队项目代码仓库管理

如何做好项目管理是目前大多数公司会面临的一个问题,那么我们今天就来讨论一下如何更好的管理项目代码仓库。

首先来看看会用到的一些关键技术或名词:

  • Git:分布式版本控制系统,目前被普遍的接受
  • gitlab/Bitbucket:是一个用于仓库管理系统的开源项目,当然这里为什么不去用github或者codding等自己可以去想想

接下来说说我司目前使用的管理方式,可能不是最好,但是基本可以解决多人协作开发并不相互覆盖的需求。

git workflow

流程

  1. 先从主仓库拉去不同的分枝,分别是master(主干分枝),pre(灰度分枝),daily(测试分枝)
  2. 开发新功能的时候都是从master上拉取最新的分枝(如f分枝)
  3. 然后在f分枝上进行开发(个人开发的话不要推送到远端)
  4. 开发完功能,要提测时就合并到daily上交给测试进行测试,测试过程中修复的bug继续在f分枝进行,然后再合并到daily,以此直到测试环境通过
  5. 测试环境通过后将f分枝合并到灰度pre分枝,并进行测试,测试过程中修复的bug继续在f分枝进行,然后在合并到daily测试完成后在合并到pre分枝(测过程也可直接合并到pre分枝测试,但需要把最终的f分支合并到daily分支),以此直到灰度环境通过
  6. 灰度环境通过测试后,pre分枝打包发部署到线上,稳定后将本地f分枝合并到master分枝,并删除f分枝

说明

以上过程中可能会有一些问题产生,具体解决方法为:

  1. 每次新功能和修复bug分枝都必须从master上拉取
  2. 避免多人同时修改同一个模块(同一块代码)

写在最后

这样可能会有人问:在pre分枝上提交了多个项目a、b、c,但是到发布的时候只有a发,b和c还在测试,这样会把b和c的代码发布到线上,怎么办?

不要着急,每个功能页面引用的静态资源都是带版本号的,即使发布到线上不去改他的引用版本号也是无关紧要哈~~

Git 子模块

为什么要用子模块

痛点

在我们开发项目的时候可能会遇到下面这个问题:在你的项目中使用另外一个项目,也许这个是一个第三方开发的库或者是你独立开发和并在多个父项目中使用的。简单的说就是A同学开发的一个模块,被B、C…同学共同调用(使用),可能就形成了如下的这种关系。

模块调用关系

这个场景下一个常见的问题产生了:你想将两个项目单独处理但是又需要在其中一个中使用另外一个。

解决方案

这种问题,Git 已经帮我们有了处理方案——子模块。

  • 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
  • 子模块关键字:git submodule

如何使用子模块

假如目前我们有两个仓库,module(目标仓库)和 submodule(子模块仓库)。

在现有的仓库中加入子模块

git submodule add https://gitee.com/Hancoson/submodule.git submodule
  • 现在你就在项目 module 里的子目录下有了一个 submodule 项目。如果你在加入子模块后立刻运行 git status,你会看到下面两项:

    On branch master
    Your branch is up-to-date with 'origin/master'.
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
        new file:   .gitmodules
        new file:   submodule
  • 查看 .gitmodules 文件

    [submodule "submodule"]
      path = submodule
      url = https://gitee.com/Hancoson/submodule.git

如果你有多个子模块,这个文件里会有多个条目。很重要的一点是这个文件跟其他文件一样也是处于版本控制之下的,就像你的 .gitignore 文件一样。它跟项目里的其他文件一样可以被推送和拉取。这是其他克隆此项目的人获知子模块项目来源的途径。

  • 提交增加的子模块到现有仓库的远程仓库

commit 方式和普通提交的方式相同。然后,module 仓库是这样的:
仓库

修改子模块

  • 切换到要修改代码的子模块分支,修改对应代码,push(和普通仓库方式相同)

  • 拉取服务器代码,并且合并到本地 module (目标仓库分支)

    git submodule update --remote --merge
  • 修改目标仓库,推送

克隆一个带子模块的项目

这里你将克隆一个带子模块的项目。当你接收到这样一个项目,你将得到了包含子项目的目录,但里面没有文件,如下:

submodule 目录存在了,但是是空的。你必须到 module (目标仓库分支)根目录下运行两个命令:git submodule init 来初始化你的本地配置文件,git submodule update 来从那个项目拉取所有数据并检出你上层项目里所列的合适的提交:

$ git submodule init
Submodule 'submodule' (https://gitee.com/Hancoson/submodule.git) registered for path 'submodule'
$ git submodule update
Submodule path 'submodule': checked out '471741e958c2ef0096ad5971c264041a9295ce3b'

现在你的 submodule 子目录就处于你先前提交的确切状态了。每次你从主项目中拉取一个子模块的变更都必须这样做 git submodule update

后续

子模块使用过程还需要大家自己多练习一下,主要的命令 git submodule *.

微信小程序开发总结

微信小程序出来也有一段时间了,之前也陆续做过demo练练手,毕竟自己的demo还是比较简单的,如今参与公司的项目也是一路踩坑而来。

认识小程序

语言及开发工具

首先,小程序类Web,但不同于我所认识的HTML,他有属于自己的开发语言及工具:

  • JavaScript: 微信小程序的 JavaScript 运行环境即不是 Browser 也不是 Node.js。它运行在微信 App 的上下文中,不能操作 Browser context 下的 DOM,也不能通过 Node.js 相关接口访问操作系统 API。
  • WXML: 作为微信小程序的展示层,并不是使用 Html,而是自己发明的基于 XML 语法。
  • WXSS: 用来修饰展示层的样式。官方的描述是 “ WXSS (WeiXin Style Sheets) 是一套样式语言,用于描述 WXML 的组件样式。WXSS 用来决定 WXML 的组件应该怎么显示。”
  • 小程序开发工具,开发者可以完成小程序的 API 和页面的开发调试、代码查看和编辑、小程序预览和发布等功能。

入口文件

小程序主要包含以下三个入口文件:

  • app.js 这个文件是整个小程序的入口文件,我们主要做了网络检测、用户信息获取等;当让也可以注入公用的方法在其他页面中去通过getApp()调用(注:页面中调用app.js中的方法时不需要通过require或者import引入)
  • app.json 这个文件可以对小程序进行全局配置,决定页面文件的路径、整体窗口表现、设置网络超时时间、设置多tab等.
  • app.wxss 是小程序的公共样式表

项目开发

本次项目只负责了首页、授权和一些公共模块的开发,接下来就着重从这些模块展开。

生命周期

当然,微信小程序和其他前端框架类似也是有生命周期的:

Page({
/**
* 页面的初始数据
*/
data: {},

/**
* 生命周期函数--监听页面加载(像首页数据请求可以放在这里)
*/
onLoad: function (options) {},

/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {},

/**
* 页面相关事件处理函数--监听用户下拉动作(这里添加了下拉刷新的功能)
*/
onPullDownRefresh: function () {},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {}
})

当我们在data中初始化的值需要修改时,可在各生命周期及方法中通过setData()修改。由于小程序的入口页面就是首页,在首页添加了用户登陆和网络状态的检测在onLoad中。

tabBar

tabBar即小程序的底部导航栏,由于微信的限制,最少2个最多5个导航栏,只可设置文案、图标。

样式

小程序样式采用WXSS语言(具有CSS大部分特性)。他也提供了一种新的单位rpx(可根据屏幕宽度自行适应)。官方规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素,既然这样我们也推荐了我们设计师采用iPhone6作为设计标准,即750px宽度。

但是在实际的开发过程中如果字体大小也使用rpx做单位的话,在iPhone6 Puls上显示文字过大,影响美观。后经过测试采用了px做单位,即原设计稿尺寸的一半+px,这样可以保证文字大小在各设备中保持一致。

网络状态

在官方文档上有明确规定,本地资源是无法通过CSS获取的,图片的话只能使用网络资源或base64方式。首页有个需要判断网络状态的需求,由于断网情况下无法获取网络资源,最后就使用了base64的方式。

官方获取网络状态的API是getNetworkType为异步接口,通过它的返回结果再进行下一步(是显示无网络还是调用数据列表接口),说到这里大家都知该怎么办了——Promise,具体封装如下

new Promise((resolve, reject) => {
  let req = wx.getNetworkType({
    success: function (res) {
      var networkType = res.networkType;
      if (networkType === 'none') {
        resolve(false)
      } else {
        resolve(true)
      }
    },
    fail() {
      reject(false)
    }
  });
})

调试

调试的时候最大的问题呢是:无论是开发者工具上还是手机上,记得先把缓存删干净再测。特别是开发者工具每次切换host都要清理缓存,再重新打开,而且出现bug的时候尽量多测几次,进行反复确定。不然的话,你可能会发现,本来测好的功能又出现问题了,或者是本来有问题的部分又没有问题了。

开发者工具

开发者工具调试界面和Chorem浏览的开发者工具类似,调试工具分为 7 大功能模块:Wxml、Console、Sources、Network、Appdata、Storage、Sensor、Trace。

真机调试

在开发环境下手机调试需要满足以下条件:

  • 必须使用开发者账号微信打开
  • 手机需要配置代理(常用charles或fiddler进行代理)
  • 在开发者工具中:设置—代理—代理设置—手动设置代理(填写本机ip127.0.0.1,端口号:8888)
  • 手机扫码(通过开发者工具预览生成二维码),点击右上角--打开调试手机上调试也可以有类似开发者工具一样的调试体验,可以看到Log、System、WeChat、WXML等,也可以看到页面的性能数据。总体来说还是挺齐全的。

些题外话

虽然 期已经成功发布,但是还有些需要修改个优化的地 ,需要添加及开发的新功能还可能很多,遇到的坑可能也只是冰山一角,有些坑只有去踩过才知道有多深。

移动端Web开发调试之Weinre调试教程

安装

环境配置

测试运行

  • $ weinre 查看是否安装成功

使用

配置

  • $ weinre --boundHost 10.2.81.58 --httpPort 8910 浏览打开 http://10.2.81.58:8910 即可看到本地安装的 weinre 首页
  • 项目中引入 <script src="http://10.2.81.58:8910/target/target-script-min.js#anonymous"></script>
  • 浏览器访问 http://10.2.81.58:8910/client/#anonymous 即可对源码进行调试

可调式包括微信浏览器、Chrome、Safari等各种内核浏览器,佷方便。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.