JiM-W

keep Moving


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

seajs核心

发表于 2017-04-27 | 分类于 seajs

研究下seajs到底是什么

seajs模块化的理念就是,每一个js文件都是一个单独的模块,模块中的变量,方法不能被外部访问,有效的防治了全局变量的污染;如果想要被外部文件访问,需要通过exports导出;(基本的原理还是立即执行函数的封装以及闭包的使用)

文件目录结构

1
2
3
4
5
6
7
F:
workspace
sea.js
seajsTest
index.html
main.js
module.js

###1 seajs全局函数

index.html

1
2
3
4
5
6
7
8
<body>
<script src='../sea.js'></script>;
<script>
console.log(seajs);
console.log(define);//define是一个全局函数
//consolelog(require);//报错,require不是全局函数,这点记住
</script>
</body>

控制台输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// console.log(seajs);
Object
Module:function t(a,b)
cache:Object
config:function (a)
data:Object
emit:function (a,b)
off:function (a,b)
on:function (a,b)
request:function o(a,b,c,d)
require:function (a)
resolve:function m(a,b)
use:function (a,b)
version:"2.2.3"
1
2
console.log(define)
function (a,c,d){var e=arguments.length;1===e?(d=a,a=b):2===e&&(d=c,y(a)?(c=a,a=b):c=b),!y(c)&&z(d)&&(c=s(""+d));var f={id:a,uri:t.resolve(a),deps:c,factory:d};if(!f.uri&&M.attachEvent){var g=r();g&&(f.uri=g.…

2 seajs.use()

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script src='../sea.js'></script>;
<script>
// seajs.use('./main.js');
// console.log(foo);//报错,这就是模块化的理念
seajs.use(['./main.js','./module'],function(){
console.log('callback is exec');
console.log(arguments);
});
// console.log(seajs);
// console.log(define);
</script>

main.js

1
2
3
4
5
6
7
8
9
10
11
12
define(function(require,exports,module){
console.log(arguments);
console.log('main.js was loaded');
var foo = 'Jhon'
exports.foo = 'bar';
exports.myFunc = function(){
console.log('this is a func from main.js');
}
})

module.js

1
2
3
4
5
define(function(require,exports,module){
exports.mod = 'baz';
console.log('module.js is exec');
})

seajs.use会按顺序加载模块,先加载main.js,那么main.js就会被执行,然后加载module.js,module.js就会被执行,等两者加载完毕之后,就会执行回调函数;回调函数中接受参数,参数是每个模块导出的exports对象;如果没有导出exports对象,那么回调函数中对应的参数是null

seajs如何判断模块的加载是否完成的呢?其实底层也是封装了script标签的onload事件,加载完毕之后触发该事件,然后执行回调函数;

针对上面的代码分析下控制台的输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//main.js输出
[function, Object, t, callee: function, Symbol(Symbol.iterator): function]
0: function a(b)
async: function (b,c)
resolve:function (a)
1:Object
foo : 'bar',
myFunc : function(){}
2: t
dependencies:Array(0)
exports:Object
id :
uri :
status :
//以上是define(factory)中factory的三个参数解析
main.js was loaded
//module.js输出
module.js is exec
//index.html 中seajs.use回调函数输出
callback is exec
//以下是seajs.use回调函数中传入的参数,可以看出来是每个模块导出的对象 exports
[Object, Object, callee: function, Symbol(Symbol.iterator): function]0: Object1: Objectcallee: function ()length: 2Symbol(Symbol.iterator): function values()__proto__: Object

3 接下来解释下factory中三个参数

1
2
3
define(function(require,exports,module){
})
1
2
3
4
5
6
7
8
9
10
11
12
13
[function, Object, t, callee: function, Symbol(Symbol.iterator): function]
0: function a(b)
async: function (b,c)
resolve:function (a)
1:Object
foo : 'bar',
myFunc : function(){}
2: t
dependencies:Array(0)
exports:Object
id :
uri :
status :

3.1 require方法解析

  • require(a) 是一个方法 ,接受模块标识作为 唯一的参数,
    • 如果require的模块有exports对象返回,返回的是a模块的exports对象;
    • 如果require的模块没有返回exports对象返回,那么默认返回null
    • 引入的模块定义的方法可以使用,比如引入jquery等类库
  • require.sync(a,callback) 是一个方法,接受两个参数,一个是模块标识,一个是回调函数,异步执行;
  • require.resolve(a) 该函数不会加载模块,只返回解析后的绝对路径。
1
2
3
4
5
6
7
8
9
define(function(require,exports,module){
exports.mod = 'baz';
console.log('module.js is exec');
require('./jquery-1.12.4.js');
console.log($);
/*
function ( selector, context ) { }
*/
})

3.1.1 当模块标识为顶级标识的时候:(不以 . 开头 或者不以 / 开头);require(a)路径的解析是以seajs文件所在目录为基准

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//main.js
define(function(require,exports,module){
console.log(arguments);
console.log('main.js was loaded');
var foo = 'Jhon'
var retMain = require('module.js');
console.log(require.resolve('module'));
//file:///F:/workspace/module.js
//可以看出顶级模块标识是以seajs文件所在目录为基准进行解析的
exports.foo = 'bar';
exports.myFunc = function(){
console.log('this is a func from main.js');
}
})

3.1.2 当模块标识为相对标识的时候(以 . 开头,则该模块标识就是相对标识), 相对标识路径解析的时候,永远是相对于当前模块而言 ;出现在define的factory函数中require函数的参数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
define(function(require,exports,module){
console.log(arguments);
console.log('main.js was loaded');
var foo = 'Jhon'
var retMain = require('./module.js');
console.log(retMain);//Object {mod: "baz"}
console.log(require.resolve('./module'));
//file:///F:/workspace//seajsTest/module.js
exports.foo = 'bar';
exports.myFunc = function(){
console.log('this is a func from main.js');
}
})

3.2 exports对象解析 exports对象是module.exports对象的引用,而模块真正导出的是module.exports

当我们在定义模块的时候

1
2
3
4
5
6
7
8
9
define(function(require, exports) {
// 错误用法!!!
exports = {
foo: 'bar',
doSomething: function() {}
};
});

对象并不会被导出,因为改变了exports的引用,所以导出的module.exports 是没有数据的

1
2
3
4
5
6
7
8
9
define(function(require, exports, module) {
// 正确写法
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});

3.3 module 对象

  • module.id
1
2
3
4
5
define('id', [], function(require, exports, module) {
// 模块代码
});
  • module.uri 根据模块系统的路径解析规则得到的模块绝对路径。

    一般情况下在没有写define中的id参数的时候,两者完全一样;

  • module.dependencies Array 是一个数组,表示当前模块的依赖

  • module.exports

    当前模块对外提供的接口。

    传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    define(function(require, exports, module) {
    // exports 是 module.exports 的一个引用
    console.log(module.exports === exports); // true
    // 重新给 module.exports 赋值
    module.exports = new SomeClass();
    // exports 不再等于 module.exports
    console.log(module.exports === exports); // false
    });

    4 seajs.use require 二者引用模块规则

    • 当引用的模块是顶级标识的时候,那么就是相对于seajs文件所在目录为基准
    • 当引用模块是相对标识的时候,那么就是相对于seajs.use requre所在html文件目录为基准;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    F:
    workspace
    sea.js
    seajsTest
    index.html
    main.js
    module.js
    module2.js
    seajsConfig.js

    seajsConfig.js

    1
    2
    3
    4
    5
    6
    seajs.config({
    alias : {
    // mod2 : 'seajsTest/module2' //这是顶级标识,引用文件的时候会相对于seajs文件所在目录
    mod2 : './module2' //这是相对路径,引用文件的时候会相对于当前文件或者当前模块所在目录
    }
    })

    index.html

    此时在index.html中通过别名引用模块module2.js 可以通过别名 mod2来引用

    注意如果引用模块有exports对象内容,那么回调函数传入的参数就是exports对象,如果没有,那么传入的是null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script src='../sea.js'></script>;
    <script src='./seajsconfig.js'></script>;
    <script>
    seajs.use(['./main.js','./module','mod2'],function(){
    console.log('callback is exec');
    console.log(arguments);
    });
    console.log(seajs);
    // console.log(define);
    </script>

    5 模块路径

    • 相对标识:以 . 开头(包括.和..),相对标识永远相对当前模块的 URI 来解析。
    • 顶级标识:不以点(.)或斜线(/)开始, 会相对模块系统的基础路径(即 SeaJS配置 的 base 路径)来解析
    • 普通路径:除了相对和顶级标识之外的标识都是普通路径,相对当前页面解析。
      • 绝对路径是普通路径。绝对路径比较容易理解。
      • 根路径是以“/”开头的,取当前页面的域名+根路径;
      • 相对路径以 ../ 开头或者以 ./ 开头的都是相对路径

webpack foundation

发表于 2017-04-27 | 分类于 webpack

webpack.config.js解析

1 entry

entry 对象是用于 webpack 查找启动并构建 bundle。其上下文是入口文件所处的目录的绝对路径的字符串。

1
context: path.resolve(__dirname, "app")
1
2
3
4
5
const config = {
entry: './path/to/my/entry/file.js'
};
module.exports = config;
1
2
3
4
5
6
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};

2 output object

output对象

1
2
3
4
5
6
7
8
const config = {
output: {
filename: 'bundle.js',
path: __dirname+'/build/'
}
};
module.exports = config;

注意output.path提供打包后的文件存放的绝对路径地址 ; output.filename提供了打包后文件的名字;

3 module

这些选项决定了如何处理项目中的不同类型的模块。

loader 用于对模块的源代码进行转换。loader 可以使你在 require() 或”加载”模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你在 JavaScript 中 require() CSS文件!

1
2
npm install --save-dev css-loader
npm install --save-dev ts-loader
1
2
3
4
5
6
7
8
9
//webpackconfig.js
module.exports = {
module: {
rules: [
{test: /\.css$/, use: 'css-loader'},
{test: /\.ts$/, use: 'ts-loader'}
]
}
};

如何使用loaders有以下三种方式

  • 通过配置
  • 在 require 语句中显示使用
  • 通过 CLI

通过 webpack.config.js

module.rules 允许你在 webpack 配置中指定几个 loader。 这是展示 loader 的一种简明的方式,并且有助于使代码变得简洁。而且对每个相应的 loader 有一个完整的概述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader'},
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}

通过 require

可以在 require 语句(或 define, require.ensure, 等语句)中指定 loader。使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

1
require('style-loader!css-loader?modules!./styles.css');

通过前置所有规则及使用 !,可以对应覆盖到配置中的任意 loader。

选项可以传递查询参数,就像在 web 中那样(?key=value&foo=bar)。也可以使用 JSON 对象(?{"key":"value","foo":"bar"})。

尽可能使用 module.rules,因为这样可以在源码中减少引用,并且可以更快调试和定位 loader,避免代码越来越糟。

通过 CLI

可选项,你也可以通过 CLI 使用 loader:

1
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'

这会对 .jade 文件使用 jade-loader,对 .css 文件使用 style-loader 和 css-loader

4 plugins

由于 plugin 可以携带参数/选项,你必须在 wepback 配置中,向 plugins 属性传入 new 实例。

根据你如何使用 webpack,这里有多种方式使用插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
const webpack = require('webpack'); //访问内置的插件
const path = require('path');
const config = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader'
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
module.exports = config;

5 target

因为服务器和浏览器代码都可以用 JavaScript 编写,所以 webpack 提供了多种构建目标(target),你可以在你的 webpack 配置中设置。

高速webpack这个程序的目标环境是什么

尽管 webpack 不支持向 target 传入多个字符串,你可以通过打包两份分离的配置来创建同构的库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//webpack.config.js
var path = require('path');
var serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
var clientConfig = {
target: 'web', // <=== 默认是 'web',可省略
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];

target描述

async-node编译为类 Node.js 环境可用(使用 fs 和 vm 异步加载分块)

atomelectron-main 的别名

electron``electron-main 的别名

electron-main编译为 Electron 渲染进程,使用 JsonpTemplatePlugin, FunctionModulePlugin 来为浏览器环境提供目标,使用 NodeTargetPlugin 和 ExternalsPlugin 为 CommonJS 和 Electron 内置模块提供目标。

node编译为类 Node.js 环境可用(使用 Node.js require 加载 chunk)

node-webkit编译为 Webkit 可用,并且使用 jsonp 去加载分块。支持 Node.js 内置模块和 nw.gui 导入(实验性质)

web编译为类浏览器环境里可用(默认)

webworker编译成一个 WebWorker

例如,当 target 设置为 "electron",webpack 引入多个 electron 特定的变量。有关使用哪些模板和 externals 的更多信息,你可以直接参考 webpack 源码。

6 命令

  • 如果存在 webpack.config.js,webpack 命令将默认选择使用它
  • 通过npm配合使用,改变package.json文件
1
2
3
4
"scripts": {
"example": "babel-node"
},
//可以通过 npm run example 执行 babel-node命令

或者

1
2
3
4
"scripts": {
"build": "webpack"
},
// 可以通过 npm run build 执行 webpack 命令

webpack

发表于 2017-04-27 | 分类于 webpack

1 WebPack的安装

  1. 安装命令

    1
    $ npm install webpack -g
  2. 使用webpack

    1
    2
    $ npm init # 会自动生成一个package.json文件
    $ npm install webpack --save-dev #将webpack增加到package.json文件中
  3. 可以使用不同的版本

    1
    $ npm install webpack@1.2.x --save-dev
  4. 如果想要安装开发工具

    1
    $ npm install webpack-dev-server --save-dev

每个项目下都必须配置有一个 webpack.config.js ,它的作用如同常规的 gulpfile.js/Gruntfile.js ,就是一个配置项,告诉 webpack 它需要做什么。

下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
index : './src/js/page/index.js'
},
//入口文件输出配置
output: {
path: 'dist/js/page',
filename: '[name].js'
},
module: {
//加载器配置
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.js$/, loader: 'jsx-loader?harmony' },
{ test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
},
//其它解决方案配置
resolve: {
root: 'E:/github/flux-example/src', //绝对路径
extensions: ['', '.js', '.json', '.scss'],
alias: {
AppStore : 'js/stores/AppStores.js',
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
};
  1. plugins 是插件项,这里我们使用了一个 CommonsChunkPlugin的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 common.js 来方便多页面之间进行复用。
  2. entry 是页面入口文件配置,output 是对应输出项配置 (即入口文件最终要生成什么名字的文件、存放到哪里)
  3. module.loaders 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理。 所有加载器需要使用npm来加载
  4. 最后是 resolve 配置,配置查找模块的路径和扩展名和别名(方便书写)

2 WebPack开始使用

这里有最基本的使用方法,给大家一个感性的认识

  1. 正确安装了WebPack,方法可以参考上面

  2. 书写entry.js文件

    1
    document.write("看看如何让它工作!");
  3. 书写index.html文件

    1
    2
    3
    4
    5
    6
    7
    8
    <html>
    <head>
    <meta charset="utf-8">
    </head>
    <body>
    <script type="text/javascript" src="bundle.js" charset="utf-8"></script>
    </body>
    </html>
  4. 执行命令,生成bundle.js文件

    1
    $ webpack ./entry.js bundle.js
  5. 在浏览器中打开index.html文件,可以正常显示出预期

  6. 增加一个content.js文件

    1
    module.exports = "现在的内容是来自于content.js文件!";
  7. 修改entry.js文件

    1
    document.write(require("./content.js"));
  8. 执行第四步的命令

进行加载器试验

  1. 增加style.css文件

    1
    2
    3
    body {
    background: yellow;
    }
  2. 修改entry.js文件

    1
    2
    require("!style!css!./style.css");
    document.write(require("./content.js"));
  3. 执行命令,安装加载器

    1
    $ npm install css-loader style-loader # 安装的时候不使用 -g
  4. 执行webpack命令,运行看效果

  5. 可以在命令行中使用loader

    1
    $ webpack ./entry.js bundle.js --module-bind "css=style!css"

使用配置文件
默认的配置文件为webpack.config.js

  1. 增加webpack.config.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
    entry: "./entry.js",
    output: {
    path: __dirname,
    filename: "bundle.js"
    },
    module: {
    loaders: [
    { test: /\.css$/, loader: "style!css" }
    ]
    }
    };
  2. 执行程序

    1
    $ webpack

发布服务器

  1. 安装服务器

    1
    2
    $ npm install webpack-dev-server -g
    $ webpack-dev-server --progress --colors
  2. 服务器可以自动生成和刷新,修改代码保存后自动更新画面

    1
    http://localhost:8080/webpack-dev-server/bundle

3 使用配置文件webpack.config.js 我们只需要执行命令 webpack即可,而如果没有配置文件,我们需要webpack指定要打包的入口文件以及文件生成的路径和文件名

seajs基础

发表于 2017-04-26 | 分类于 seajs

模块标识

模块标识是一个字符串,用来标识模块。在 require、 require.async 等加载函数中,第一个参数都是模块标识。

  • 顶级标识始终相对 base 基础路径解析。
  • 绝对路径和根路径始终相对当前页面解析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
Sea.js 中的模块标识是 CommonJS 模块标识 的超集:
一个模块标识由斜线(/)分隔的多项组成。
每一项必须是小驼峰字符串、 . 或 .. 。
模块标识可以不包含文件后缀名,比如 .js 。
模块标识可以是 相对 或 顶级 标识。如果第一项是 . 或 ..,则该模块标识是相对标识。
顶级标识根据模块系统的基础路径来解析。
相对标识相对 require 所在模块的路径来解析。
注意,符合上述规范的标识肯定是 Sea.js 的模块标识,但 Sea.js 能识别的模块标识不需要完全符合以上规范。 比如,除了大小写字母组成的小驼峰字符串,Sea.js 的模块标识字符串还可以包含下划线(_)和连字符(-), 甚至可以是 http://、https://、file:/// 等协议开头的绝对路径。
相对标识
相对标识以 . 开头,只出现在模块环境中(define 的 factory 方法里面)。相对标识永远相对当前模块的 URI 来解析:
// 在 http://example.com/js/a.js 的 factory 中:
require.resolve('./b');
// => http://example.com/js/b.js
// 在 http://example.com/js/a.js 的 factory 中:
require.resolve('../c');
// => http://example.com/c.js
顶级标识
顶级标识不以点(.)或斜线(/)开始, 会相对模块系统的基础路径(即 Sea.js 的 base 路径)来解析:
// 假设 base 路径是:http://example.com/assets/
// 在模块代码里:
require.resolve('gallery/jquery/1.9.1/jquery');
// => http://example.com/assets/gallery/jquery/1.9.1/jquery.js
模块系统的基础路径即 base 的默认值,与 sea.js 的访问路径相关:
如果 sea.js 的访问路径是:
http://example.com/assets/sea.js
则 base 路径为:
http://example.com/assets/
当 sea.js 的访问路径中含有版本号时,base 不会包含 seajs/x.y.z 字串。 当 sea.js 有多个版本时,这样会很方便。
如果 sea.js 的路径是:
http://example.com/assets/seajs/1.0.0/sea.js
则 base 路径是:
http://example.com/assets/
当然,也可以手工配置 base 路径:
seajs.config({
base: 'http://code.jquery.com/'
});
// 在模块代码里:
require.resolve('jquery');
// => http://code.jquery.com/jquery.js
普通路径
除了相对和顶级标识之外的标识都是普通路径。普通路径的解析规则,和 HTML 代码中的 <script src="..."></script> 一样,会相对当前页面解析。
// 假设当前页面是 http://example.com/path/to/page/index.html
// 绝对路径是普通路径:
require.resolve('http://cdn.com/js/a');
// => http://cdn.com/js/a.js
// 根路径是普通路径:
require.resolve('/js/b');
// => http://example.com/js/b.js
// use 中的相对路径始终是普通路径:
seajs.use('./c');
// => 加载的是 http://example.com/path/to/page/c.js
seajs.use('../d');
// => 加载的是 http://example.com/path/to/d.js
提示:
顶级标识始终相对 base 基础路径解析。
绝对路径和根路径始终相对当前页面解析。
require 和 require.async 中的相对路径相对当前模块路径来解析。
seajs.use 中的相对路径始终相对当前页面来解析。
文件后缀的自动添加规则
Sea.js 在解析模块标识时, 除非在路径中有问号(?)或最后一个字符是井号(#),否则都会自动添加 JS 扩展名(.js)。如果不想自动添加扩展名,可以在路径末尾加上井号(#)。
// ".js" 后缀可以省略:
require.resolve('http://example.com/js/a');
require.resolve('http://example.com/js/a.js');
// => http://example.com/js/a.js
// ".css" 后缀不可省略:
require.resolve('http://example.com/css/a.css');
// => http://example.com/css/a.css
// 当路径中有问号("?")时,不会自动添加后缀:
require.resolve('http://example.com/js/a.json?callback=define');
// => http://example.com/js/a.json?callback=define
// 当路径以井号("#")结尾时,不会自动添加后缀,且在解析时,会自动去掉井号:
require.resolve('http://example.com/js/a.json#');
// => http://example.com/js/a.json

1 seajs.use 用来加载一个模块

加载的模块中的代码会被执行,所以加载的模块暴露出来的方法都可以使用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seajs.use(id, callback?)
// 加载一个模块
seajs.use('./a');
// 加载一个模块,在加载完成时,执行回调
seajs.use('./a', function(a) {
a.doSomething();
});
// 加载多个模块,在加载完成时,执行回调
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.doSomething();
});

注意:seajs.use 与 DOM ready 事件没有任何关系。如果某些操作要确保在 DOM ready 后执行,需要使用 jquery 等类库来保证。比如

1
2
3
4
5
seajs.use(['jquery', './main'], function($, main) {
$(document).ready(function() {
main.init();
});
});
1
seajs.use("abc/main"); //导入seajs.js同级的abc文件夹下的main.js模块的(后缀名可略去不写).因为这是顶级标识

##2 seajs.configObject

所谓的配置文件可以理解为对模块标识的一个变量表示,进行简化模块标识的操作;

baseUrl object

1
2
3
4
seajs.config({
// Sea.js 的基础路径(修改这个就不是路径就不是相对于seajs文件了)
base: 'http://example.com/path/to/base/'
});

aliasObject

别名配置,配置之后可在模块中使用require调用 require('jquery');

用变量表示文件,解决路径层级过深和实现路径映射

1
2
3
seajs.config({
alias: { 'jquery': 'jquery/jquery/1.10.1/jquery' }
});
1
2
3
4
define(function(require, exports, module) {
//引用jQuery模块
var $ = require('jquery');
});

pathsObject

设置路径,方便跨目录调用。通过灵活的设置path可以在不影响base的情况下指定到某个目录。

(用变量表示路径,解决路径层级过深的问题)

1
2
3
4
5
6
7
8
9
10
11
seajs.config({
//设置路径
paths: {
'gallery': 'https://a.alipayobjects.com/gallery'
},
// 设置别名,方便调用
alias: {
'underscore': 'gallery/underscore'
}
});
1
2
3
4
define(function(require, exports, module) {
var _ = require('underscore');
//=> 加载的是 https://a.alipayobjects.com/gallery/underscore.js
});

varsObject

变量配置。有些场景下,模块路径在运行时才能确定,这时可以使用 vars 变量来配置。

vars 配置的是模块标识中的变量值,在模块标识中用 {key} 来表示变量。

1
2
3
4
5
6
seajs.config({
// 变量配置
vars: {
'locale': 'zh-cn'
}
});
1
2
3
4
define(function(require, exports, module) {
var lang = require('./i18n/{locale}.js');
//=> 加载的是 path/to/i18n/zh-cn.js
});

mapArray

该配置可对模块路径进行映射修改,可用于路径转换、在线调试等。

1
2
3
4
5
seajs.config({
map: [
[ '.js', '-debug.js' ]
]
});
1
2
3
4
define(function(require, exports, module) {
var a = require('./a');
//=> 加载的是 path/to/a-debug.js
});

preloadArray

使用preload配置项,可以在普通模块加载前,提前加载并初始化好指定模块。

preload中的空字符串会被忽略掉。

1
2
3
4
5
6
7
// 在老浏览器中,提前加载好 ES5 和 json 模块
seajs.config({
preload: [
Function.prototype.bind ? '' : 'es5-safe',
this.JSON ? '' : 'json'
]
});

注意:preload中的配置,需要等到 use 时才加载。比如:

1
2
3
4
5
6
seajs.config({
preload: 'a'
});
// 在加载 b 之前,会确保模块 a 已经加载并执行好
seajs.use('./b');

preload 配置不能放在模块文件里面:

1
2
3
4
5
6
7
seajs.config({
preload: 'a'
});
define(function(require, exports) {
// 此处执行时,不能保证模块 a 已经加载并执行好
});

debugBoolean

值为true时,加载器不会删除动态插入的 script 标签。插件也可以根据debug配置,来决策 log 等信息的输出。

baseString

Sea.js 在解析顶级标识时,会相对 base 路径来解析。

注意:一般请不要配置 base 路径,把 sea.js 放在合适的路径往往更简单一致。

charsetString | Function

获取模块文件时,

Redux React 基本环境的搭建

发表于 2017-04-26 | 分类于 redux

Redux环境的基本搭建

reactContext

发表于 2017-04-25 | 分类于 react

npm

发表于 2017-04-25 | 分类于 npm

本地安装

npm install 模块名

  • \1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  • \2. 可以通过 require() 来引入本地安装的包。

全局安装

  • \1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  • \2. 可以直接在命令行里使用。

模块操作

创建模块

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

更新模块

$ npm update module

npm install msbuild:

  • 会把msbuild包安装到node_modules目录中
  • 不会修改package.json
  • 之后运行npm install命令时,不会自动安装msbuild

npm install msbuild –save:

  • 会把msbuild包安装到node_modules目录中
  • 会在package.json的dependencies属性下添加msbuild
  • 之后运行npm install命令时,会自动安装msbuild到node_modules目录中
  • 之后运行npm install –production或者注明NODE_ENV变量值为production时,会自动安装msbuild到node_modules目录中

npm install msbuild –save-dev:

  • 会把msbuild包安装到node_modules目录中
  • 会在package.json的devDependencies属性下添加msbuild
  • 之后运行npm install命令时,会自动安装msbuild到node_modules目录中
  • 之后运行npm install –production或者注明NODE_ENV变量值为production时,不会自动安装msbuild到node_modules目录中

使用原则:

运行时需要用到的包使用–save,否则使用–save-dev。

通过package.json进行安装

如果我们的项目依赖了很多package,一个一个地安装那将是个体力活。我们可以将项目依赖的包都在package.json这个文件里声明,然后一行命令搞定

1
npm install

npm ls:查看安装了哪些包

运行如下命令,就可以查看当前目录安装了哪些package

npm ls pkg:查看特定package的信息

运行如下命令,输出grunt-cli的信息

npm update pkg:package更新

1
npm update grunt-cli

npm search pgk:搜索

输入如下命令

1
npm search grunt-cli

packjson文件属性解释

npmRyf

npm常用命令总结

npm命令详解

Redux

发表于 2017-04-25 | 分类于 redux

建议先看这篇GitHub上的基础 Redux基础 深入浅出Redux3

1 action 创建函数 仅仅是一个函数,作用是用来生成一个action,action本质是一个javascript对象

这个函数会返回一个 Action 对象,这个对象里描述了“页面发生了什么”。随后这个对象会被传入到 Reducer 中。

官方定义:Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

Action 是把数据从应用(这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。

描述发生了什么

1
2
3
4
5
6
7
var actionCreator = function() {
// ...that creates an action (yeah, the name action creator is pretty obvious now) and returns it
return {
type: 'AN_ACTION',
name:'Jhon'
}
}

Redux约定Action内使用一个字符串类型的type字段来表示将要执行的动作,比如上栗的 AN_ACTION,除了type之外可以存放其他要操作的数据;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {createStore} from 'redux';
var userReducer = function(state = {},action){
console.log('userReducer was called with ',state,'and action',action);
switch(action.type){
case 'SET_NAME' :
return {
...state,
name : action.name
}
default :
return state ;
}
}
var store_0 = createStore(userReducer);

传入store_0的可以是一个action对象

1
2
3
4
store_0.dispatch({type:'SET_NAME',name : "Jhon"});
//userReducer was called with {} and action { type: 'SET_NAME', name: 'Jhon' }
console.log('store_0 state after action',store_0.getState());
////store_0 state after action { name: 'Jhon' }

同样也可以是一个action创建函数,该函数返回action对象

1
2
3
4
5
6
7
8
9
10
11
12
function setNameActionCreator(name){
return {
type : 'SET_NAME',
name
//这里用到了ES6声明对象的简洁语法
}
}
store_0.dispatch(setNameActionCreator('JiM'));
//userReducer was called with { name: 'Jhon' } and action { type: 'SET_NAME', name: 'JiM' }
console.log('store_0 state after action',store_0.getState());
//store_0 state after action { name: 'JiM' }

2 reducer

根据action更新state,reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。

官方定义:Actions describe the fact that something happened, but don’t specify how the application’s state changes in response. This is the job of a reducer.

之所以称作 reducer 是因为它将被传递给 Array.prototype.reduce(reducer, ?initialValue) 方法。保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()。
1
2
3
4
5
6
(previousState, action) => newState
//How do I handle data modifications?
// Using reducers (called "stores" in traditional flux).
// A reducer is a subscriber to actions.
// A reducer is just a function that receives the current state of your application, the action,
// and returns a new state modified (or reduced as they call it)
1
2
3
4
5
6
7
8
9
10
function todoApp(state = {}, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}

传入state的默认值可以是null undefined [ ] 布尔类型 字符串等 ,也就是说,reducer函数可以处理任何数据结构类型,根据需求不同可以传入不同的state默认值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {createStore} from 'redux';
function myReducer(state = null,action){
console.log('myReducer was called with state',state,'and action',action);
switch(action.type){
case 'SOMETHING' :
return {
...state ,
message : 'done'
}
default :
return state
}
}
var store_0 = createStore(myReducer)

如果随着我们的业务越来越复杂,如何让不同的reducer进行合并呢?redux提供了combineReducers函数,该函数接受一个对象,会执行传入的所有的reducer;

combineReducers 生成了一个类似于Reducer的函数。为什么是类似于,因为它不是真正的Reducer,它只是一个调用Reducer的函数,只不过它接收的参数与真正的Reducer一模一样~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import {createStore,combineReducers} from 'redux';
var userReducer = function(state = {},action){
console.log('userReducer was called with the state',state,'and action',action);
switch(action.type){
case 'SOmething' :
return {
...state,
message:action.value
}
default :
return state ;
}
}
var itemsReducer = function(state = [],action){
console.log('itemsReducer was called with the state',state,'and action',action);
switch(action.type){
//etc
default :
return state ;
}
}
var reducer = combineReducers({userReducer,itemsReducer});
//其实等价于 var reducer = combineReducers({userReducer:userReducer,itemsReducer:itemsReducer});
//ES6对象新的语法特性
//这行代码执行之后输出
/*
userReducer was called with the state {} and action { type: '@@redux/INIT' }
userReducer was called with the state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_q.y.p.r.j.q.z.6.7.x.t.b.a.6.j.b.r.z.f.r' }
itemsReducer was called with the state [] and action { type: '@@redux/INIT' }
itemsReducer was called with the state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_f.y.3.u.2.q.q.w.d.h.q.c.w.p.g.2.p.g.b.9' }
*/
console.log(combineReducers);//[Function: combineReducers]
console.log(reducer);//[Function: combination] 可以看出combineReducers这个函数调用一系列的reducer然后返回一个新的reducer函数
var store_0 = createStore(reducer);
/**
* userReducer was called with the state {} and action { type: '@@redux/INIT' }
itemsReducer was called with the state [] and action { type: '@@redux/INIT' }
*/
console.log('store_0 state after initialization',store_0.getState());
//输出如下: store_0 state after initialization { userReducer: {}, itemsReducer: [] }

看下combineReducers底层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function combineReducers(reducers) {
// 过滤reducers,把非function类型的过滤掉~
var finalReducers = pick(reducers, (val) => typeof val === 'function');
// 一开始我一直以为这个没啥用,后来我发现,这个函数太重要了。它在一开始,就已经把你的State改变了。变成了,Reducer的key 和 Reducer返回的initState组合。
var defaultState = mapValues(finalReducers, () => undefined);
return function combination(state = defaultState, action) {
// finalReducers 是 reducers
var finalState = mapValues(finalReducers, (reducer, key) => {
// state[key] 是当前Reducer所对应的State,可以理解为当前的State
var previousStateForKey = state[key];
var nextStateForKey = reducer(previousStateForKey, action);
return nextStateForKey;
});
// finalState 是 Reducer的key和stat的组合。。
}
}

从上面的源码可以看出,combineReducers 生成一个类似于Reducer的函数combination。

当使用combination的时候,combination会把所有子Reducer都执行一遍,子Reducer通过action.type 匹配操作,因为是执行所有子Reducer,所以如果两个子Reducer匹配的action.type是一样的,那么都会成功匹配。

注意:

  1. 不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, {visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。

    1
    2
    state = {name:'Jhon',age:18} {...state,address : 'China'}
    => { name: 'Jhon', age: 18, address: 'China' }
  2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。if you don’t, you’ll end up having your reducer return undefined (and lose your state).

    我们可以看下源代码为什么必须返回一个state;如下所示,如果reducer函数没有显式返回state,那么 currentReducer()函数执行之后,函数的默认返回值是undefined;这就是为什么我们会失去state的根本原因;

    在使用combineReducers方法时,它也会检测你的函数写的是否标准。如果不标准,那么会抛出一个大大的错误!!

    1
    2
    // currentState 是当前的State,currentReducer 是当前的Reducer
    currentState = currentReducer(currentState, action);

    ​

  3. 注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据

3 store

store就是将action和reducers联系到一起的对象;注意也就是一个javascript对象,这个对象里面有一些方法;

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 唯一一个方法更新 state;action是一个对象,可以直接传入对象,也可以传入定义action
  • 通过 subscribe(listener) 注册监听器,每当state状态改变的时候,注册的函数就会被调用
  • 通过 replaceReducer(listener) 返回的函数注销监听器。用于替换创建store的reducer
  • Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

官方定义:In the previous sections, we defined the actions that represent the facts about “what happened” and the reducers that update the state according to those actions.

The Store is the object that brings them together. The store has the following responsibilities:

  • Holds application state;
  • Allows access to state via getState();
  • Allows state to be updated via dispatch(action);
  • Registers listeners via subscribe(listener).

如何创建一个store?

1
2
//创建Store非常简单。createStore 有两个参数,Reducer 和 initialState。
let store = createStore(rootReducers, initialState);

...是对象扩展运算符,会将对象展开 , 同时也会将数组展开

1
2
3
4
5
6
import { createStore } from 'redux'
var reducer = function (...args) {
console.log('Reducer was called with args', args)
}
var store_1 = createStore(reducer) ;//
1
2
3
4
5
//创建的store_1打印输出如下,可以看出store_1是一个对象,该对象有如下的方法(函数);
{ dispatch: [Function: dispatch],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer] }

当我们创建reduex实例的时候,需要传递一个函数,也就是说createStore( reducer ,optional) 第一个参数需要传入函数处理逻辑;reducer是一个函数,reducer接受两个参数,第一个是state 第二是 action;

当执行createStore的时候,传入的第一个函数会被执行

1
2
3
4
5
6
7
8
9
function addItem(text) {
return {
type: types.ADD_ITEM,
text
}
}
// 新增数据
store.dispatch(addItem('Read the docs'));

看下dispatch底层是如何实现的:dispatch 核心源码

1
2
3
4
5
6
7
8
9
10
function dispatch(action) {
// currentReducer 是当前的Reducer
currentState = currentReducer(currentState, action);
listeners.slice().forEach(function (listener) {
return listener();
});
return action;
}

可以看到其实就是把当前的Reducer执行了。并且传入State和Action。

dispatch异步实现

subscribe(listener) 可以使我们的state状态改变的时候,将数据的变化反映到视图view层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import {createStore,combineReducers} from 'Redux';
var itemReducers = function(state = [],action){
console.log('itemsReducers was called with ',state,'and action',action);
switch(action.type){
case 'ADD_ITEM' :
return [
...state,
action.item
]
default :
return state ;
}
}
var reducers = combineReducers({items : itemReducers });
var store_0 = createStore(reducers);
console.log(store_0.getState());
store_0.subscribe(function(){
//这个事件只有在state改变了才会触发,此时不会触发,因为还没有改变state,没有传入action
console.log('store_0 has been updated,lasted store state ',store_0.getState());
//update your view
})
var addItemActionCreator = function(item){
return {
type : 'ADD_ITEM',
item
}
}
store_0.dispatch(addItemActionCreator({id:4,description:'anything'}));
//这一行代码执行之后,上面的subscribe函数才会被触发

jsx in depth

发表于 2017-04-24 | 分类于 react

1 在JSX中我们可以使用 . 来引用一个组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="root"></div>
<script type='text/babel'>
function TestOne (props){
const style = {color : 'red'}
return (
<div style = {style}>
this is red Component {props.flag}
</div>
);
}
const MyComponent = { TestOne : TestOne }
function TestTwo (){
return (
<MyComponent.TestOne />
);
}
ReactDOM.render(
<TestTwo />,
document.getElementById('root')
)
</script>

2 JSX中的props属性详解

2.1 属性值可以是js表达式,也可以是字符串,默认值是true;以下属性可以通过 props.foo props.message props.autocomplete进行访问;

1
2
3
4
5
6
7
<MyComponent foo={1 + 2 + 3 + 4} />
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />

2.2 Children in JSX

首先来了解下定义,也就是说,在有开闭标签组件的中的内容就是 props.children对象对应的值

In JSX expressions that contain both an opening tag and a closing tag, the content between those tags is passed as a special prop: props.children. There are several different ways to pass children:

2.2.1 字符串 作为props.chidlren

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="root"></div>
<script type='text/babel'>
function MyComponent (props){
return (
<div>
{props.children}
</div>
);
}
ReactDOM.render(
<MyComponent >this is passed by props.children</MyComponent>,
document.getElementById('root')
)
</script>

2.2.2 js表达式 作为props.children

首先要了解 React会将数组中的内容展开然后渲染到页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="root"></div>
<script type='text/babel'>
function MyComponent(props){
return (
<ul>
{props.children}
</ul>
);
}
const numbers = [1,2,3,4];
const listItems = numbers.map((number)=>{return <li>{number}</li>})
ReactDOM.render(
<MyComponent >{listItems}</MyComponent>,
document.getElementById('root')
)
</script>

2.2.3 JSX 表达式也可以作为 props.children 传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="root"></div>
<script type='text/babel'>
function MyComponent(props){
return (
<ul>
{props.children}
</ul>
);
}
function MyInnerComponent (){
return (
<li>this is innerCmponent</li>
);
}
ReactDOM.render(
<MyComponent >
<MyInnerComponent />
</MyComponent>,
document.getElementById('root')
)
</script>

2.2.4 函数作为props.children传入

没有被函数操作之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="root"></div>
<script type='text/babel'>
function Repeat(){
let arr = [];
for(let i = 0 ; i < 10 ; i++){
arr.push(i)
}
return (
<div>
{arr}
</div>
);
}
ReactDOM.render(
<Repeat />,
document.getElementById('root')
)
</script>

被props.children函数操作之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="root"></div>
<script type='text/babel'>
function Repeat(props){
let arr = [];
for(let i = 0 ; i < 10 ; i++){
arr.push(props.children(i))
}
return (
<div>
{arr}
</div>
);
}
ReactDOM.render(
<Repeat>{(index)=>index*2}</Repeat>,
document.getElementById('root')
)
</script>

2.2.5 null undefined false true 都不会被渲染

false, null, undefined, and true are valid children. They simply don’t render. These JSX expressions will all render to the same thing:

1
2
3
4
5
6
7
8
9
10
<div id="root"></div>
<script type='text/babel'>
ReactDOM.render(
<div>
{null }
</div>,
document.getElementById('root')
)
</script>

Refs And DOM

发表于 2017-04-24 | 分类于 react

1 ref 属性是一个函数的时候

当一个组件被加载完成之后,ref指向的函数会被执行。

React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.

When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="root"></div>
<script type='text/babel'>
class CustomTextInput extends React.Component{
constructor(props){
super(props);
this.focus = this.focus.bind(this);
}
focus(){
this.textInput.focus();
console.log(this.textInput);
console.log(this);
}
render(){
return(
<div>
<input type="text" ref = {(input)=>this.textInput = input}/>
<input type="button" value="Focus on this" onClick = {this.focus}/>
</div>
);
}
}
ReactDOM.render(
<CustomTextInput />,
document.getElementById('root')
)
</script>

When the ref attribute is used on a custom component declared as a class, the refcallback receives the mounted instance of the component as its argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script type='text/babel'>
class App extends React.Component{
constructor(props){
super(props)
}
render(){
return(
<Counter ref={(el)=>{
console.log(el);
el.sayHello();
}}></Counter>
)
}
}
class Counter extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
console.log('mounted');
}
sayHello(){
console.log('hello JiM ');
}
render(){
return(
<div>
Counter
</div>
)
}
}
console.log(<App />);
ReactDOM.render(
<App />,
document.getElementById('root')
)
</script>

2 当ref属性是一个字符串的时候

注意:官方文档已经不建议使用字符串作用ref属性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div id="root"></div>
<script type='text/babel'>
class CustomTextInput extends React.Component{
constructor(props){
super(props);
this.focus = this.focus.bind(this);
}
focus(){
console.log(this);
console.log(this.refs);//object {myFocus:input}
this.refs.myFocus.focus();
}
render(){
return(
<div>
<input type="text" ref = 'myFocus' />
<input type="button" value="Focus on this" onClick = {this.focus}/>
</div>
);
}
}
ReactDOM.render(
<CustomTextInput />,
document.getElementById('root')
)
</script>

如果 ref 是设置在原生 HTML 元素上,它拿到的就是 DOM 元素,如果设置在自定义组件上,它拿到的就是组件实例,这时候就需要通过 findDOMNode 来拿到组件的 DOM 元素。

  • 你可以使用 ref 到的组件定义的任何公共方法,比如 this.refs.myTypeahead.reset()
  • Refs 是访问到组件内部 DOM 节点唯一可靠的方法
  • Refs 会自动销毁对子组件的引用(当子组件删除时)

ref属性可以实现父组件访问子组件这样的通信流

3 Uncontrolled Components

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

To write an uncontrolled component, instead of writing an event handler for every state update, you can use a ref to get form values from the DOM.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div id="root"></div>
<script type='text/babel'>
class NameForm extends React.Component{
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e){
console.log(this.input);
console.log(this.input.value);
e.preventDefault();
}
render(){
return(
<form onSubmit = {this.handleSubmit}>
<input type="text" ref = {(input)=>{console.log(input);console.log(this);
this.input = input ;
}}/>
<input type="submit" value="click to submit" ref = {(input)=>{console.log(input);this.input = input ;
}}/>
</form>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById("root")
)
</script>

以上代码案例可以解释箭头函数中的内部this指向

  • 箭头函数内部的this指向class类
  • this.input = input 决定 当前类的 input指向哪个 input标签,后面的会覆盖掉前面的

4 React 中的 value属性值 defaultValue

In the React rendering lifecycle, the value attribute on form elements will override the value in the DOM. With an uncontrolled component, you often want React to specify the initial value, but leave subsequent updates uncontrolled. To handle this case, you can specify a defaultValue attribute instead of value.

如果我们添加value = ‘this can not be changed’给input标签,那么该标签是无法重新写入内容的

1
2
3
4
5
6
7
8
<form onSubmit = {this.handleSubmit}>
<input type="text" value = 'this can not be changed' ref = {(input)=>{console.log(input);console.log(this);
this.input = input ;
}}/>
<input type="submit" value="click to submit" ref = {(input)=>{console.log(input);this.input = input ;
}}/>
</form>

改成defaultValue可以重新写入 defaultValue = ‘this can not be changed’

除了input之外 Likewise, <input type="checkbox"> and <input type="radio"> support defaultChecked, and <select> and <textarea> supports defaultValue.也是一样的道理

1…345…20
JiM-W

JiM-W

keep fighting!

195 日志
52 分类
90 标签
© 2017 JiM-W
由 Hexo 强力驱动
主题 - NexT.Pisces