hancoson / blog Goto Github PK
View Code? Open in Web Editor NEW:notebook:前端博客:clap: :star::star::star:鼓励一下:point_right:
Home Page: http://www.vsoui.com/
:notebook:前端博客:clap: :star::star::star:鼓励一下:point_right:
Home Page: http://www.vsoui.com/
React
组件非常的强大和灵活,提供了强大的功能,但是随着时间的推移,它将变的越来越臃肿和繁琐。
与任何其他类型的编程相比,React
组件遵循单一功能原则( single responsibility principle),它不仅使你的组件更容易维护,更重要的是复用性。然而,如何负责一个庞大的React
组件不再是一件容易的事。下面从易到难我买将有有三种方法具体讲解这个问题。
render()
方法这个是最常见的方法:当一个组件要呈现太多的元素,把这些元素分解为逻辑子组件是一个简单的简化方法。
一个常见而快速的方法就是在同一类中拆分render()
方法并创建额外的“sub-render”
方法:
class Panel extends React.Component {
renderHeading() {
// ...
}
renderBody() {
// ...
}
render() {
return (
<div>
{this.renderHeading()}
{this.renderBody()}
</div>
);
}
}
虽然这中方法有它的用处,但这不是一个真正的拆分组件本身的方法。每个state
、props
和class 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()
是一个树形的元素,只有走到PanelHeader
和 PanelBody
,而不是在它们之下的所有元素。
来看个实际意义的测试:一个浅渲染可以用来轻松隔离这些单位进行独立的测试。作为奖励,当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>
);
}
}
另一个组件可以找出的独有的metadata
和actions
。
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。简单的解决方案可能是向Document
的componentDidMount
和componentWillUnmount
生命周期方法添加代码,如下所示:
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-redux
,styled-components
和react-intl
。毕竟,这些库都是解决任何React
应用程序的通用方面。另一个库——recompose
,通过使用HOCs进一步管理从组件状态到生命周期方法的一切。
React
组件是高度可组合的设计。通过轻松分解和组合它们,可以利用这一点。
不要躲避创建小而重要的组件。起初它可能会让你感到尴尬,但结果将是更强大和可重用的代码。
原文:https://medium.com/dailyjs/techniques-for-decomposing-react-components-e8a1081ef5da
var name = 'Bob';
没有初始化的变量自动获取一个默认值为 null。
int lineCount;
assert(lineCount == null);
// Variables (even if they will be numbers) are initially null.
在声明变量的时候,你可以选择加上具体 类型:
String name = 'Bob';
添加类型可以更加清晰的表达你的意图。 IDE 编译器等工具有可以使用类型来更好的帮助你, 可以提供代码补全、提前发现 bug 等功能。
如果你以后不打算修改一个变量,使用 final 或者 const。 一个 final 变量只能赋值一次;一个 const 变量是编译时常量。 (Const 变量同时也是 final 变量。) 顶级的 final 变量或者类中的 final 变量在 第一次使用的时候初始化。
Dart 内置支持下面这些类型:
使用三个单引号或者双引号也可以 创建多行字符串对象
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
如果 expr1
是 non-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
语句俩打断代码的执行。 下面介绍如何使用断言。 下面是一些示例代码:
// 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: scheme
。 package: scheme
指定的库通过包管理器来提供, 例如 pub
工具。
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';
Deferred loading
(也称之为 lazy loading
) 可以让应用在需要的时候再 加载库。 下面是一些使用延迟加载库的场景:
deferred as
来 导入:import 'package:deferred/hello.dart' deferred as hello;
当需要使用的时候,使用库标识符调用 loadLibrary()
函数来加载库:
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
使用元数据给代码添加更多的信息。
元数据是以@
开始的修饰符。在`@`` 后面接着编译时的常量或调用一个常量构造函数。
目前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']
);
}
}
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 与 PNG、JPEG 的文件体积。下面来看看其中一条:
JPG 转 WebP – 压缩大小比较:
WebP 平均减小了 85.87% 的文件提交。加载时间降低了 11%,页面整体大小减少了 29%。
PNG 转 WebP – 压缩大小比较:
WebP 平均减小了42.8% 的文件提交。加载时间降低了 3%,页面整体大小减少了 25%。
好了,我还是再举一个实际的例子看看 PNG 和 Webp 格式图片的差距:
尽管只有一张图片,也是可以看出可观的差距来。
众所周知,我们的活动页面整体大小的 50% 以上都被图片占用了,所以对图片做优化就迫在眉睫了。
把 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秒 |
参考资料:
在React
中的组件可以分为presentational components
即UI(演示)组件和container components
即容器组件。今天就来说说他们两个的区别以及这样的好处。
我的演示组件:
this.props.children
进行控制。Flux
的action
和stores
。props
接受和会掉数据。我的容器组件:
Flux/redux
操作,并将其作为回调给演示组件。Reat
Redux
的connect()
),来自Relay
的createContainer()
或来自Flux
Utils
的Container.create()
生成的,而不是用手写。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
上不起作用!其他浏览器和手机都可以,唯独Safari
不行!幸好后面找到了方法,记录如下。
body {
position:fixed;
overflow-y:hidden;
}
这种方法在我的测试中是work的,但是可能会导致页面布局改变。
/* 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;
取决于用户代理。桌面浏览器(包括火狐浏览器)使用默认值,约为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 }
<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 is a predictable state container for JavaScript apps. —— Redux
Simple, scalable state management. —— Mobx
从 Redux 和 Mobx 官方的介绍很容易可以看出,它们都是用来管理应用的 state。但是它们有什么区别呢?刚好最近把之前使用 Reudx 做的 React 项目使用 Mobx 重构了,那么就来总结一下吧。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
在 Redux 中,最为核心的概念就是 action 、reducer、store 以及 state,那么具体是什么呢?
严格的单向数据流是 Redux 架构的设计核心。
Redux 应用中数据的生命周期遵循下面 4 个步骤:
核心库:
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 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。
比起Redux,Mobx基于观察者模式,采用多节点管理数据,是一个很轻量、入手简单、代码耦合小的数据框架。
MobX 为单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。
它由几个部分组成:Actions、State、Computed Values、Reactions。使用 MobX 将一个应用变成响应式的可归纳为以下步骤:
MobX 为现有的数据结构(如对象,数组和类实例)添加了可观察的功能。 通过使用 @observable
装饰器来给你的类属性添加注解就可以简单地完成这一切。这样改属性就变成了“被观察者”。
class Store {
@observable a = 'Hello Word!';
}
observer
函数装饰器可以用来将 React 组件转变成响应式组件。 它用 mobx.autorun
包装了组件的 render
函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。 observer
是由单独的 mobx-react
包提供的。
@observer
class Index extends Component {
render() {
return (
<p>
{this.props.Store.a}
</p>
)
}
}
这样 Index 组件就变成了一个响应式的组件(智能组件),当“被观察者”改变时,该组件就会自动更新。
React 组件通常在新的堆栈上渲染,这使得通常很难弄清楚是什么导致组件的重新渲染。 当使用 mobx-react 时可以定义一个新的生命周期钩子函数 componentWillReact
。当组件因为它观察的数据发生了改变,它会安排重新渲染,这个时候 componentWillReact
会被触发。这使得它很容易追溯渲染并找到导致渲染的操作(action)。
inject
函数装饰器可以将 Store 数据注入到组件当中。inject
是由单独的 mobx-react
包提供的。
@inject("store")
使用 MobX,可以定义在相关数据发生变化时自动更新的值。 通过 @computed
装饰器调用 的getter / setter 函数来进行使用。
class ItemsStore {
@observable items = [];
@computed get total() {
return this.items.length;
}
}
当添加了一个新的 items 时,MobX 会确保 total 自动更新。
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 包装/装饰器只会影响当前运行的函数,而不会影响当前函数调度(但不是调用)的函数! 这意味着如果你有一个 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 需要用到 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 。
最近看到关于数组遍历的东西,顺便总结一下 forEach()
和 map()
遍历数组方法的区别。这两个方法都是 ES5
中新增的,当然说到新增方法,不能不提它们的兼容性:IE 9+
,哈哈……又是 IE
。
forEach()
和 map()
都支持3个参数,即(item,index,Array)
,第一个是遍历的当前项,第二个是当前项的下标,第三个是原数组this
都是指windown
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(); 基本用法跟forEach方法类似:
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遍历数组
* @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;
}
$.each()
里面的匿名函数支持2个参数:当前项的索引i,数组中的当前项v。如果遍历的是对象,k 是键,v 是值。有返回值,可以return 出来。$.map()
里面的匿名函数支持2个参数和$.each()
里的参数位置相反:数组中的当前项v,当前项的索引 i。如果遍历的是对象,k 是键,v 是值。
安装必要的软件,这里就不详细说了,具体可参考官方文档
安装React-Native Cli npm install -g create-react-native-app
Xcode 安装升级到 8+
初始化RN项目 react-native init rnHelloWord
,可能会比较慢,要耐心等到
项目启动后,点击模拟器command+d
- 选择 Enable Live Reload
,监控RN代码的改动(热替换),可直接修改代码,即可查看最新的修改。
这样基本工作就完成了,可以点击Run
启动啦~~
不要着急,我们可以在手机上设置-通用-设备管理-添加信任
然后就可以正常启动了
应用启动后,只需要摇一摇手机可以调出调试菜单,里面具体功能同模拟器一样,这里就不再说明了。
接下来就可以开发了。
React-Native
运行环境安装难于上青天,安装成功后你离成功就不远了。它的语法相近于React
,撸起袖子~~~写代码吧。
我刚刚开始使用React
。在我的项目中,我创建了一个button
。当用户点击这个创建的button
时候,有些复杂的字母“a”出现在页面上。
然而,在我的html中,这个字母在按钮的兄弟元素当中,下面代码是我如何解决这个渲染元素的问题。
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."
如果一个框架运行时给出这样的错误提示,我就要注意了,所以看下面……
此时,最好的方法可能是设置原始组件在 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
?)都会需要获取用户是否登录,并在登录的情况反馈到前端,前端跳转到登录页面。这个就可以使用这个拦截器来实现。
在请求或响应被 then
或 catch
处理前拦截它们。
// 添加响应拦截器
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()
})
每个守卫方法接收三个参数:
Route
即将要进入的目标Route
当前导航正要离开的路由Function:
一定要调用该方法来 resolve
这个钩子。执行效果依赖 next
方法的调用参数。
confirmed
(确认的)。URL
改变了(可能是用户手动或者浏览器后退按钮),那么 URL
地址会重置到 from
路由对应的地址。next('/')
或者 next({ path: '/' }):
跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home'
之类的选项以及任何用在 router-link
的 to prop
或 router.push
中的选项。next
的参数是一个 Error
实例,则导航会被终止且该错误会被传递给 router.onError()
注册过的回调。确保要调用 next
方法,否则钩子就不会被 resolved
。
前端单页面权限控制在不同的框架中实现思路基本一致。感兴趣的同学可以去研究一下。
高阶函数(Higher Order Function)作为函数式编程众多风格中的一项显著特征,经常被使用着。
那什么是高阶函数呢?高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数,或者是返回它们,即满指至少满足下列条件之一的函数:
实际上我们日常开发中也会经常用到高阶函数。接下来看一下几个典型的应用实例:
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 并没有质的变化。
修饰器是一个对类进行处理的函数。
感兴趣的同学可以自己去研究高阶组件(HOC)和 Decorator,这里不展开说明了。
├── 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 (本地已压缩环境服务启动命令)
项目名全部采用小写方式, 以中划线分隔。 比如:kaowo-intro
采用小写方式,多个单词组成时,采用中划线连接方式,比如说: kaowo-intro.html
语法
HTML5 doctype
字符编码
<head>
<meta charset="UTF-8">
</head>
属性顺序
id
class
name
data-*
src, for, type, href
title, alt
aria-*, role
减少标签数量
<!-- Not so great -->
<span class="avatar">
<img src="...">
</span>
<!-- Better -->
<img class="avatar" src="...">
H5项目中所有的css全部使用less通过grunt编译生成。
语法
声明顺序:
Positioning 处在第一位,因为他可以使一个元素脱离正常文本流,并且覆盖盒模型相关的样式。盒模型紧跟其后,因为他决定了一个组件的大小和位置。
*Class 命名 *
完全避免 == != 的使用, 用严格比较条件 === !==
*缩进 分号 空行 *
*变量 常量 *
*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 ];
*注释 *
/*
* 注释内容与星标前保留一个空格
*/
函数声明
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
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 是世界上“最好”的语言,我不是 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 模版的实现,相关文章>>。
├─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 来说说使用方法:
git clone https://github.com/Hancoson/node-blog-app.git
yarn
brew install mongodb
mongod
# or
brew services start mongodb
最后执行:
mongo
use {nodeApp}
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
应用中,组件之间通信是常用到的技术方案。在父子组件之间通常通过props
来传递参数,而非父子组件就比较麻烦了,要么就一级一级通过props
传递,要么就使用Redux
orMobx
这类状态管理的状态管理库,但是这样无疑增加了应用的复杂度。在 FEers 的期盼中,React
团队终于从16.3.0
版本开始新增了一个新的 APIContext
,福音啊。好了,今天我就来一起学习一下这个新的Context
。
Context 目的是为了共享可以被认为是 React
组件“全局”树的数据。例如当前应用的主题、首选语言等等。接下来看看通过 props
和 Context
两种方式实现按钮组件样式参数传递方式的对比:
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 实例
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>
);
}
创建一个 Context
,React.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
类似 react-redux
中的 Provider
组件,用来注入全局的 data
(允许 Consumer
订阅 Context
的变化)。一个 Provider
可以连接到多个 Consumer
。
Consumer
组件,表示要消费 Provider
传递的数据(订阅 Context
的响应组件)。当 Provider
发生变化的时候,所有的 Consumer
都会被 re-rendered
。
新 Context
的引入,一定程度上可以减少不少项目对 redux
全家桶的依赖,从而降低了项目的复杂程度,何乐而不为呢~~
最近终于有时间�静下心来学学node
相关的知识了,那么来做一个什么东西呢?想来想去还是来做一个简单的bolg吧~~。接下来就介绍一下具体的历程。
本文章将用具体的项来介绍 node-app:https://github.com/Hancoson/node-app
node
提供的模块,中间件,在用express
创建项目时,产生node_modules
即表示M
层ejs
,mongoose
,morgan
,body-parser
等等express
生成项目时会产生views
,即前端res.render
来渲染ejs
文件ejs
模板引擎渲染index.ejs
文件app.js
中写入require('./routes/index')(app)
即可引入�;Controller
来实现具体数据的展示app.get('/articles/:id', articles.getArticle);
主要来处理业务逻辑,也就是说数据该怎么展示由他来管理,具体实现如下:
function (req, res) {
blogdbs.find({
_id: req.params.id //查询条件
}, function (err, data) {
if (err) {
//err
} else {
res.render('articles', {
... //数据对象
});
}
})
}
说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()
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
这个是android原生浏览器的一个bug,解决办法:
if(/Android 4\.[0-3]/.test(navigator.appVersion)){
window.addEventListener("resize", function(){
if(document.activeElement.tagName=="INPUT"){
window.setTimeout(function(){
document.activeElement.scrollIntoViewIfNeeded();
},0);
}
})
}
学习内存,缓存和垃圾回收相关知识 (creeperyang)
自从 ES6 诞生以来,异步编程的方法得到了很大的发展。从
Promise
到 ES7 提案中的async/await
。目前,它仍处于提案阶段,async
函数可以说是目前异步操作最好的解决方案,是对Generator
函数的升级和改进。那么今天就来具体说说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 的用法只有这些,我们在学习了基础以后,更要把它与其他的知识相结合起来,才能写出更可靠更优质的代码!
最近抽空看了看移动端适配的一些文章,也结合自己的经验做一下总结以及对比。
那么,开始正题,首先说说到目前位置出现的一些关于移动端适配的技术方案:
CSS3
的meida queries
flex
弹性布局rem
+viewport
缩放rem
方式meida queries
的方式可以说是我早期采用的布局方式,它主要是通过查询设备的宽度来执行不同的 css
代码,最终达到界面的配置。核心语法是:
@media screen and (max-width: 600px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/
/*你的css代码*/
}
media query
可以做到设备像素比的判断,方法简单,成本低,特别是对移动和PC维护同一套代码的时候。目前像Bootstrap
等框架使用这种方式布局Flex
弹性布局以天猫的实现方式进行说明:
它的viewport
是固定的:<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
高度定死,宽度自适应,元素都采用px
做单位。
随着屏幕宽度变化,页面也会跟着变化,效果就和PC页面的流体布局差不多,在哪个宽度需要调整的时候使用响应式布局调调就行(比如网易新闻),这样就实现了『适配』。DEMO>>
rem
+viewport
缩放这也是淘宝使用的方案,根据屏幕宽度设定 rem
值,需要适配的元素都使用 rem
为单位,不需要适配的元素还是使用 px
为单位。
根据rem
将页面放大dpr
倍, 然后viewport
设置为1/dpr
.
viewport
设置为1/3, 这样页面整体缩回原始大小. 从而实现高清。这样整个网页在设备内显示时的页面宽度就会等于设备逻辑像素大小,也就是device-width
。这个device-width
的计算公式为:
设备的物理分辨率/(devicePixelRatio * scale)
,在scale
为1的情况下,device-width = 设备的物理分辨率/devicePixelRatio
。
具体请查看 https://github.com/amfe/lib-flexible 或 https://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
+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);
}
缺点:
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;
}
优点:
缺点:
最近利用空余时间研究学习了前端“三剑客”之 —— 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
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 装饰器是一个函数,它接受一个元数据对象,该对象的属性用来描述这个模块。其中最重要的属性如下。
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 {
/*
……
*/
}
路由定义 会告诉路由器,当用户点击某个链接或者在浏览器地址栏中输入某个 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)有两个属性:
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 和 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是2016年10月发布,目前在Github上获取了3.4w+的 star。官方给出的解释是:快速、可靠、安全的依赖管理。
node 升级到了 8.0 后 npm 也随之升到 5.0,相比与老的 npm,npm5 有了巨大的变化,
npm install git+https://github.com/chalk/chalk.git#semver:1.0.0
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"init:runtime-only": "vue init webpack vue-cms"
}
npm run init:runtime-only
npx vue init webpack vue-cms
下面是测试了npm 4.6.1,npm 5.5.1和 yarn安装速度的对比:来源
通过对比可以看出,npm 从 4 到 5,有了很大的飞跃,yarn与npm5对比速度在大部分正常场景下还是略高一筹,特别是有缓存的情况下,不过相比之下 npm5 的差距已经很小。
通过以上一系列对比,我们可以看到 npm5 在速度和使用上确实有了很大提升。但从速度上来说 yarn 貌似还是更快一点。但是 npm 社区似乎更加的活跃,后续的发展也很值得期待的。
推荐使用~,只会修复版本的bug,比较稳定。使用^ ,有的小版本更新后会引入新的问题导致项目不稳定。
或者版本号写*,这意味着安装最新版本的依赖包,但缺点同上,可能会造成版本不兼容,慎用!
Vuejs火了有一段时间了,但是之前自己还没有一个线上项目中使用它。刚搞公司前一段时间有个新项目要开发,再加上大BOSS那边要求使用Vuejs
来做,自己也就毫不犹豫的接了下来,并用了Vuejs
作为开发框架。那为什么要用它呢?有以下原有:
data
和view
构建工具方面参考了尤大大的[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第三方的,没有自己去造轮子。
目录结构:
├─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 //工具函数
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的过程当中开始逐渐喜欢上了这个优美而简洁的框架。因此也愿意跟更多的人分享使用它的经验。也欢迎大家一起交流。
前几天在使用
ES6
的时候发现箭头函数中,事件无法通过this
获取本身,后来查了一些资料是这样的:
一般来说,this
和 event.currentTarget
是一致的。
但是,如果使用了某种作用域替换,(比如jquery.Proxy
,或者 ES6
、CoffeeScript
等里用了 =>
),this
可能有别的含义了,用 event.currentTarget
就更安全。
一般人们更容易混淆的是 event.target
和 event.currentTarget
。
父组件关键代码:
<template>
<Child :child-msg="msg"></Child>
</template>
子组件关键代码:
export default {
name: 'child',
props: {
child-msg: String //这里指定了字符串类型,如果类型不一致会警
}
};
child-msg
为父组件给子组件设置的额外属性值,属性值需在子组件中设置props
,子组件中可直接使用child-msg
变量。
子组件通过 $parent
获得父组件,通过 $root
获得最上层的组件。
简单的来说,就是在父组件中通过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
通行方式是解决兄弟组件之间通信的最佳方式。使用方法:
定义一个新的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的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
本规范是基于JavaScript规范拟定的,只针对ES6相关内容进行约定
如变量命名,是否加分号等约定的请参考JavaScript规范
应注意目前的代码转换工具(如Babel,Traceur)不够完善,有些特性须谨慎使用
对于只在当前作用域下有效的变量,应使用
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
对于常量应使用
const
进行声明,命名采用驼峰写法对于使用 immutable 数据应用
const
进行声明注意:
const
与let
只在声明所在的块级作用域内有效
// 不好
let someNum = 123;
const AnotherStr = '不变的字符串';
let arr = ['不', '变', '数', '组'];
var ANOTHER_OBJ = {
'不变对象': true
};
// 好
const someNum = 123;
const anotherStr = '不变的字符串';
const arr = ['不', '变', '数', '组'];
const anotherObj = {
'不变对象': true
};
以反引号( ` )标示
可读性更强,代码更易编写
注意排版引起空格的问题,使用场景为声明HTML模板字符串
// 不好
const tmpl = '<div class="content"> \n' +
'<h1>这是换行了。</h1> \n' +
'</div>';
// 好
const tmpl = `
<div class="content">
<h1>这是换行了。</h1>
</div>`;
// 不好
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// 好
function sayHi(name) {
return `How are you, ${name}?`;
}
// 不好
let obj = {
'one': [
{
'newTwo': [
{
'three': [
'four': '太多层了,头晕晕'
]
}
]
}
]
};
// 好
let obj = {
'one': [
'two',
{
'twoObj': '结构清晰'
}
]
};
// 不好
[(a)] = [11]; // a未定义
let { a: (b) } = {}; // 解析出错
// 好
let [a, b] = [11, 22];
对象解构 元素与顺序无关
对象指定默认值时仅对恒等于undefined ( !== null ) 的情况生效
// 不好
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);
}
// 不好
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
// 语法错误
let a;
{ a } = { b: 123};
数组元素与顺序相关
let x = 1;
let y = 2;
// 不好
let temp;
temp = x;
x = y;
y = temp;
// 好
[x, y] = [y, x]; // 交换变量
const arr = [1, 2, 3, 4, 5];
// 不好
const one = arr[0];
const two = arr[1];
// 好
const [one, two] = arr;
Set
, Map
)转为真正数组采用
Array.from
进行转换
// 不好
function foo() {
let args = Array.prototype.slice.call(arguments);
}
// 好
function foo() {
let args = Array.from(arguments);
}
结合
Set
结构与Array.from
使用indexOf,HashTable等形式,不够简洁清晰
// 好
function deduplication(arr) {
return Array.from(new Set(arr));
}
采用数组扩展
...
形式
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];
采用
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]
箭头函数更加简洁,并且绑定了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);
}
};
函数体只有单行语句时,允许写在同一行并去除花括号
当函数只有一个参数时,允许去除参数外层的括号
// 好
const foo = x => x + x; // 注意此处会隐性return x + x
const foo = (x) => {
return x + x; // 若函数体有花括号语句块时须进行显性的return
};
[1, 2, 3].map( x => x * x);
// 不好
let test = x => { x: x }; // 花括号会变成语句块,不表示对象
// 好
let test = x => ({ x: x }); // 使用括号可正确return {x:x}
使用箭头函数
// 不好
(function() {
console.log('哈');
})();
// 好
(() => {
console.log('哈');
})();
arguments
, 采用rest语法...
代替rest参数是真正的数组,不需要再转换
注意:箭头函数中不能使用
arguments
对象
// 不好
function foo() {
let args = Array.prototype.slice.call(arguments);
return args.join('');
}
// 好
function foo(...args) {
return args.join('');
}
采用函数默认参数赋值语法
// 不好
function foo(opts) {
opts = opts || {};// 此处有将0,''等假值转换掉为默认值的副作用
}
// 好
function foo(opts = {}) {
console.log('更加简洁,安全');
}
更加简洁
函数方法不要使用箭头函数,避免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);
}
};
PascalCased
)// 好
class SomeClass {
}
类中的方法定义时, 方法名与左括号
(
之间【不保留】空格间距类中的方法定义时,右括号
)
须与花括号{
【保留】一个空格间距
// 不好
class Foo{
constructor(){
// 右括号 `)` 须与花括号 `{` 仅保留一个空格间距
}
sayHi() {
}
_say () {
// 方法名与左括号 `(` 之间【不保留】空格间距
}
}
// 好
class Foo {
constructor() {
// 右括号 `)` 须与花括号 `{` 仅保留一个空格间距
}
sayHi() {
}
_say() {
// 方法名与左括号 `(` 之间【不保留】空格间距
}
}
constructor
get/set
公用访问器,set
只能传一个参数lowerCamelCase
)get/set
私有访问器,私有相关命名应加上下划线 _
为前缀// 好
class SomeClass {
constructor() {
// constructor
}
get aval() {
// public getter
}
set aval(val) {
// public setter
}
doSth() {
// 公用方法
}
get _aval() {
// private getter
}
set _aval() {
// private setter
}
_doSth() {
// 私有方法
}
}
new
// 不好
function Foo() {
}
const foo = new Foo();
// 好
class Foo {
}
const foo = new Foo();
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}`);
}
}
class不存在hoist问题,应先定义class再实例化
使用继承时,应先定义父类再定义子类
// 不好
let foo = new Foo();
class SubFoo extends Foo {
}
class Foo {
}
// 好
class Foo {
}
let foo = new Foo();
class SubFoo extends Foo {
}
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;
}
}
import / export
来做模块加载导出,不使用非标准模块写法跟着标准走的人,运气总不会太差
// 不好
const colors = require('./colors');
module.exports = color.lightRed;
// 好
import { lightRed } from './colors';
export default lightRed;
import / export
后面采用花括号{ }
引入模块的写法时,须在花括号内左右各保留一个空格// 不好
import {lightRed} from './colors';
import { lightRed} from './colors';
// 好
import { lightRed } from './colors';
方便调用方使用
// 不好
const lightRed = '#F07';
export lightRed;
// 好
const lightRed = '#F07';
export default lightRed;
import
不使用统配符 *
进行整体导入确保模块与模块之间的关系比较清晰
// 不好
import * as colors from './colors';
// 好
import colors from './colors';
import
与export
混合在一行分开导入与导出,让结构更清晰,可读性更强
// 不好
export { lightRed as default } from './colors';
// 好
import { lightRed } from './colors';
export default lightRed;
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 (c) 2016 Hancoson
好几年之前【大前端】这个词语就开始在“dev er”中流行起来了,那么大前端到底包含了哪些技术呢?传统的FE、Native(Hybrid)、Node、图形技术、VR……,今天我们来着重说说其中简单的一块——Nodejs(请求转发)。
明确用 Node
来干什么,很重要。
很明显这样可以完全抛弃 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 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 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 # 恢复最后一次提交的状态
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 # 查看提交统计信息
Mac上可以使用tig代替diff和log,brew install tig
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> # 强制删除某个分支 (未被合并的分支被删除的时候需要强制)
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 stash # 暂存
git stash list # 列所有stash
git stash apply # 恢复暂存的内容
git stash drop # 删除暂存区
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
Nuxt.js 是一个基于 Vue.js 的服务端渲染框架。
下图阐述了 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
方法是 Nuxt.js 对 Vue 扩展的方法。 会在组件(限于页面组件)每次初始化前被调用的。所以该方法没有办法通过this
来引用组件的实例对象。它可以在服务端或路由更新之前被调用。可以利用asyncData
方法来获取数据并返回给当前组件。
asyncData (context) {
return { project: 'nuxt' }
}
asyncData
的参数(context
)有哪些?
Nuxt renderer 使用vue-server-renderer
插件创建渲染器并解析 webpack 打好的 bundle 文件
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中是失效的。
每个用户通过浏览器访问 Vue 页面时,都是一个全新的上下文,但在服务端,应用启动后就一直运行着,处理每个用户请求的都是在同一个应用上下文中。为了不串数据,需要为每次 SSR 请求,创建全新的 app, store, router。
我们主要关注最后一部分asyncData部分
首先会根据上下文环境中的url调用 将匹配的component返回
接着遍历每个component,根据component的asyncData配置,执行 promisify()来promise化asyncData方法并将上下文对象赋给asyncData方法
promisify()方法接受两个参数:第一个组件中配置的asyncData()方法;第二个是挂载到新vue实例上的上下文对象
执行后通过方法将得到的数据同步一份给页面中定义的data,asyncData只是在首屏的时候调用一次,后续交互还是交给client处理
在开发nuxt项目的时候,我们难免会使用到window
或者document
来获取dom元素。如果直接在文件中使用就会报错。这是因为window
或者document
是浏览器端的东西服务端并没有。
解决方法:我们只需要在使用的地方通过process.browser
/process.server
来判断
if (process.browser) {
// 浏览器中运行代码
}
如果在状态树中指定了 nuxtServerInit
方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。
举个例子,假设我们服务端的会话状态树里可以通过 req.session.user
来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js
如下:
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}
nuxt
在 1.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
初始化项目时,必须选择集成服务器框架,如express
、koa
,只有这样才具有服务端中间件扩展的功能。
对于使用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,然后靠 router-middleware 从 context.headers.cookie
获取并注入
算法技能如今越来越被看重,今天开始把平时记录的一些关于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
兼容性需要考虑;
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
}
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;
}
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;
}
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)); //返回左边数组+基准值+右边数组
}
两种方式:官网下载和github下载
cd ~/development
unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip
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
这时候它会将你未安装的依赖一一列出,每个电脑缺少的依赖都不尽相同,按照给出的提示安装插件即可。直到你的开发工具通过验证即可,如:
至此,全部环境搭建步骤结束。
经过上面三部环境基本上差不多了,现在来建立一个Flutter项目。
进入你想存放项目的目录,执行命令行:
flutter create flutterapp
其中flutterapp
为项目名称,不能用大写。
flutter run
当今 React
比较流行,可能会有很多的新手和我一样遇到这个问题:
var MyClass = React.createClass({...});
和class MyClass extends React.Component{...}
之间的区别是什么?那么今天带大家一起学习一下。
React
中有两种支持创建组件的方法,你可以通过 React.createClass
和 extends 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 */ };
},
});
- 这两种写法的方法名首字母都必须大写
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来绑定
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: ''
};
//...
}
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
(混入)是面向对象编程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 在团队开发中使用已经越来越普遍,那么如何做好提交信息的规范化呢?本文主要结合 Angular Git Commit message 规范,来介绍一下相关的配套工具使用。
下面主要介绍一下相关的工具。
commitizen是一个撰写合格 Commit message 的工具。
安装如下:
$ npm i -g commitizen
然后,在项目目录里,运行下面的命令,使其支持 Angular 的 Commit message 格式。
$ commitizen init cz-conventional-changelog --save --save-exact
以后,凡是用到git commit
命令,一律改为使用git cz
。这时,就会出现选项,用来生成符合格式的 Commit message。
如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成
例子
生成的文档包括以下三个部分。
每个部分都会罗列相关的 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
如何做好项目管理是目前大多数公司会面临的一个问题,那么我们今天就来讨论一下如何更好的管理项目代码仓库。
首先来看看会用到的一些关键技术或名词:
接下来说说我司目前使用的管理方式,可能不是最好,但是基本可以解决多人协作开发并不相互覆盖的需求。
master
(主干分枝),pre
(灰度分枝),daily
(测试分枝)master
上拉取最新的分枝(如f
分枝)f
分枝上进行开发(个人开发的话不要推送到远端)daily
上交给测试进行测试,测试过程中修复的bug继续在f
分枝进行,然后再合并到daily
,以此直到测试环境通过f
分枝合并到灰度pre
分枝,并进行测试,测试过程中修复的bug继续在f
分枝进行,然后在合并到daily
测试完成后在合并到pre
分枝(测过程也可直接合并到pre
分枝测试,但需要把最终的f
分支合并到daily
分支),以此直到灰度环境通过pre
分枝打包发部署到线上,稳定后将本地f
分枝合并到master
分枝,并删除f
分枝以上过程中可能会有一些问题产生,具体解决方法为:
master
上拉取这样可能会有人问:在pre
分枝上提交了多个项目a、b、c,但是到发布的时候只有a发,b和c还在测试,这样会把b和c的代码发布到线上,怎么办?
不要着急,每个功能页面引用的静态资源都是带版本号的,即使发布到线上不去改他的引用版本号也是无关紧要哈~~
在我们开发项目的时候可能会遇到下面这个问题:在你的项目中使用另外一个项目,也许这个是一个第三方开发的库或者是你独立开发和并在多个父项目中使用的。简单的说就是A同学开发的一个模块,被B、C…同学共同调用(使用),可能就形成了如下的这种关系。
这个场景下一个常见的问题产生了:你想将两个项目单独处理但是又需要在其中一个中使用另外一个。
这种问题,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,他有属于自己的开发语言及工具:
小程序主要包含以下三个入口文件:
getApp()
调用(注:页面中调用app.js中的方法时不需要通过require
或者import
引入)本次项目只负责了首页、授权和一些公共模块的开发,接下来就着重从这些模块展开。
当然,微信小程序和其他前端框架类似也是有生命周期的:
Page({
/**
* 页面的初始数据
*/
data: {},
/**
* 生命周期函数--监听页面加载(像首页数据请求可以放在这里)
*/
onLoad: function (options) {},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {},
/**
* 页面相关事件处理函数--监听用户下拉动作(这里添加了下拉刷新的功能)
*/
onPullDownRefresh: function () {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {}
})
当我们在data
中初始化的值需要修改时,可在各生命周期及方法中通过setData()
修改。由于小程序的入口页面就是首页,在首页添加了用户登陆和网络状态的检测在onLoad
中。
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。
在开发环境下手机调试需要满足以下条件:
虽然 期已经成功发布,但是还有些需要修改个优化的地 ,需要添加及开发的新功能还可能很多,遇到的坑可能也只是冰山一角,有些坑只有去踩过才知道有多深。
node
环境安装;npm install weinre -g
npm 项目地址$ 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等各种内核浏览器,佷方便。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.