React v16.0

新的渲染返回类型:碎片和字符串

现在可以从组件的渲染方法中返回一个包含元素的数组

1
2
3
4
5
6
7
8
9
render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}

添加了对返回字符串的支持

1
2
3
render() {
return 'Look ma, no spans!';
}

更好的服务端渲染

React 16 完全重写服务器渲染,支持流。同时编译不再进行 process.env 检查(Node 读取 process.env 非常慢)。并且比 React 15 快大概三倍

支持自定义 DOM 属性

React 现在会将自定义属性传递给 DOM,而不是忽略不认识的 HTMLSVG 属性。这使得我们能够不必在维护的 React 特性的白名单,并能够减少文件体积

1
2
// Your code:
<div mycustomattribute="something" />
1
2
// React 15 output:
<div />
1
2
// React 16 output:
<div mycustomattribute="something" />

减少文件体积

React 现在使用 Rollup 来进行扁平化的打包以处理不同目标格式,而这使得体积和性能都有了提高。相较于之前的版本体积减少了32%

MIT 协议

新核心架构

异步渲染 - 一种周期性地对浏览器执行调度渲染工作的策略。结果如下,通过异步渲染,应用能够更好的响应,因为 React 避免阻塞了主线程。

新的弃用

保留(Hydrating)服务端渲染的容器现在有了更清晰的 API 定义。若你想重用服务端渲染的 HTML,使用 ReactDOM.hydrate 而不是 ReactDOM.render。若你只是想做客户端渲染则继续使用 ReactDOM.render 即可。

更新

  • React 15 已对使用 unstable_handleError 进行了限制,不再为错误边界提供文档支持。该方法已重命名为 componentDidCatch。你可以使用 codemod 来自动地迁移代码到新的 API

  • 通过 null 调用 setState 不再触发更新。这允许你确定在更新函数里你是否想要重新渲染。

  • setState 回调函数(第二个参数)现在会在 componentDidMount / componentDidUpdate 之后立刻触发,而非等到所有组件都已渲染。

  • 当使用 <B /> 来替换 <A />B.componentWillMount 现在会在 A.componentWillUnmount 之前触发。之前,在某些情况下,A.componentWillUnmount 会立刻触发。

JavaScript环境要求

React 16 依赖于集合类型 MapSet。若你要支持老式的可能未提供原生支持的浏览器和设备(例如 IE < 11),考虑在你的应用库中包含一个全局的 polyfill,例如 core-jsbabel-polyfill

一个使用 core-js 支持老版浏览器的 React 16 polyfill 环境大致如下:

1
2
3
4
5
6
7
8
9
10
import 'core-js/es6/map';
import 'core-js/es6/set';

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

ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);

评论和共享

this.props.children

this.props.children

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const NotesList = React.createClass({
render: function() {
return (
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
);
}
});

ReactDOM.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.body
);

这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。更多的 React.Children 的方法,请参考官方文档。

评论和共享

React 生命周期

初始化阶段:

函数名 说明
constructor(props) 构造函数
componentWillMount render之前最后一次修改状态的机会
render 只能访问this.props和this.state,只有一个顶层组件,不允许修改状态和DOM输出
componentDidMount 成功render并渲染完成真实DOM之后触发,可以修改DOM

运行中阶段:

函数名 说明
componentWillReceiveProps 父组件修改属性触发,可以修改属性、修改状态
shouldComponentUpdate 组件是否要更新,返回false会阻止render调用
componentWillUnpdate 不能修改状态和属性
render 只能访问this.props和this.state,不允许修改状态和DOM输出
componentDidUpdate 可以修改DOM

销毁阶段:

函数名 说明
componentWillUnmount 在删除组件之前进行清理操作,比如计时器和事件处理器
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
import React, { Component } from 'react'

class GD extends Component{

constructor(props) {
super(props)
console.log('constructor')
this.state = {
shrinkState: true
}
}

componentWillMount() {
console.log('componentWillMount')
}

render() {
return (
<div>
<h3>GD</h3>
<input
type="button"
onClick={()=>{
this.setState({
shrinkState: false
})
}}
value={this.props.name}
/>
</div>
)
}

componentDidMount() {
console.log('componentDidMount')
}

componentWillReceiveProps() {
console.log('componentWillReceiveProps: 父组件修改属性触发')
}

shouldComponentUpdate() {
console.log('shouldComponentUpdate')
return true
}

componentWillUpdate() {
console.log('componentWillUpdate')
}

componentDidUpdate() {
console.log('componentDidUpdate')
}

componentWillUnmount (){
console.log('componentWillUnmount')
}
}

export default GD

评论和共享

histroy 属性

Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。
history属性,一共可以设置三种值。

  • browserHistory
  • hashHistory
  • createMemoryHistory
    如果设为 hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似 example.com/#/some/path
    1
    2
    3
    4
    5
    6
    import { hashHistory } from 'react-router'

    render(
    <Router history={hashHistory} routes={routes} />,
    document.getElementById('app')
    )

如果设为 browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径 example.com/some/path,背后调用的是浏览器的 History API

1
2
3
4
5
6
import { browserHistory } from 'react-router'

render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)

但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。
如果开发服务器使用的是 webpack-dev-server,加上 --history-api-fallback参数就可以了。

1
$ webpack-dev-server --inline --content-base . --history-api-fallback

或者在 webpack.config.js 配置

1
2
3
devServer: {
historyApiFallback: true,
},

createMemoryHistory主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动。

1
const history = createMemoryHistory(location)

评论和共享

Redux 笔记

介绍

动机

JavaScript 需要更多的 stateRedux 试图让 state 的变化变得可预测。

三大原则

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store

State 是只读的

惟一改变 state 的方法就是触发 actionaction(表达想要修改的意图) 是一个用于描述已发生事件的普通对象。

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers

Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。

基础

Action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。

Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。

例如:

1
2
3
4
{
type: ADD_TODO,
text: 'Build my first Redux app'
}

action 对象的结构完全由自己决定,但是为了多人合作方便,最好遵循一定的标准 Flux 标准 Action

store 里能直接通过 store.dispatch() 调用 dispatch()

但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。

Reducer

处理 action

reducer 就是一个纯函数,接收旧的 stateaction,返回新的 state。持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:

  • 修改传入参数
  • 执行有副作用的操作,如 API 请求和路由跳转
  • 调用非纯函数,如 Date.now() 或 Math.random()

建议你尽可能地把 state 范式化,不存在嵌套。把应用的 state 想像成数据库。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

注意:

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

拆分 Reducer

Redux 提供了 combineReducers() 工具类来合并拆分的 Reducer

Store

Redux 应用只有一个单一的 store

Store 就是把 ActionReducer 联系到一起的对象。Store 有以下职责:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。

数据流

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

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

  1. 调用 store.dispatch(action)
  2. Redux store 调用传入的 reducer 函数。

    Store 会把两个参数传入 reducer: 当前的 state 树和 action。

  3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  4. Redux store 保存了根 reducer 返回的完整 state 树。

搭配 React

安装 React Redux

Redux 默认并不包含 React 绑定库,需要单独安装。

1
npm install --save react-redux

connect()

我们想要通过 react-redux 提供的 connect() 方法将包装好的组件连接到 Redux。尽量只做一个顶层的组件,或者 route 处理。从技术上来说你可以将应用中的任何一个组件 connect()Redux store 中,但尽量避免这么做,因为这个数据流很难追踪。

任何一个从 connect() 包装好的组件都可以得到一个 dispatch 方法作为组件的 props,以及得到全局 state 中所需的任何内容。connect() 的唯一参数是 selector。此方法可以从 Redux store 接收到全局的 state,然后返回组件中需要的 props。最简单的情况下,可以返回一个初始的 state (例如,返回认证方法),但最好先将其进行转化。

selector 的作用就是为 React Components 构造适合自己需要的状态视图。是一个自定义函数,selector 的实现完全是自定义函数

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
import { connect } from 'react-redux';
...

class App extends Component {
render() {
// 通过调用 connect() 注入:
const { dispatch, visibleTodos, visibilityFilter } = this.props
return (
...
)
}
}

App.propTypes = {
...
}

// 基于全局 state ,哪些是我们想注入的 props ?
// 注意:使用 https://github.com/reactjs/reselect 效果更佳。
function select(state) {
return {
visibilityFilter: state.visibilityFilter
};
}

// 包装 component ,注入 dispatch 和 state 到其默认的 connect(select)(App) 中;
export default connect(select)(App);

reselect 这个项目提供了带 cache 功能的 selector。如果 Store/State 和构造 view 的参数没有变化,那么每次 Component 获取的数据都将来自于上次调用/计算的结果。得益于 Store/State Immutable 的本质,状态变化的检测是非常高效的。

高级

异步Action

当调用异步 API 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻 (也可能是超时)。

这两个时刻都可能会更改应用的 state;为此,你需要 dispatch 普通的同步 action。一般情况下,每个 API 请求都需要 dispatch 至少三种 action:

  1. 一种通知 reducer 请求开始的 action。

    对于这种 action,reducer 可能会切换一下 state 中的 isFetching 标记。以此来告诉 UI 来显示进度条。

  2. 一种通知 reducer 请求成功结束的 action。

    对于这种 action,reducer 可能会把接收到的新数据合并到 state 中,并重置 isFetching。UI 则会隐藏进度条,并显示接收到的数据。

  3. 一种通知 reducer 请求失败的 action。

    对于这种 action,reducer 可能会重置 isFetching。或者,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。

为了区分这三种 action,可能在 action 里添加一个专门的 status 字段作为标记位:

1
2
3
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

又或者为它们定义不同的 type:

1
2
3
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

异步 Action Creator

如何把之前定义的同步 action creator 和 网络请求结合起来呢?标准的做法是使用 Redux Thunk middleware。要引入 redux-thunk 这个专门的库才能使用。

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
import fetch from 'isomorphic-fetch'

export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}

export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
return {
type: RECEIVE_POSTS,
subreddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}

// 来看一下我们写的第一个 thunk action creator!
// 虽然内部操作不同,你可以像其它 action creator 一样使用它:
// store.dispatch(fetchPosts('reactjs'))

export function fetchPosts(subreddit) {

// Thunk middleware 知道如何处理函数。
// 这里把 dispatch 方法通过参数的形式传给函数,
// 以此来让它自己也能 dispatch action。

return function (dispatch) {

// 首次 dispatch:更新应用的 state 来通知
// API 请求发起了。

dispatch(requestPosts(subreddit))

// thunk middleware 调用的函数可以有返回值,
// 它会被当作 dispatch 方法的返回值传递。

// 这个案例中,我们返回一个等待处理的 promise。
// 这并不是 redux middleware 所必须的,但这对于我们而言很方便。

return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json =>

// 可以多次 dispatch!
// 这里,使用 API 请求结果来更新应用的 state。

dispatch(receivePosts(subreddit, json))
)

// 在实际应用中,还需要
// 捕获网络请求的异常。
}
}

本示例使用了 fetch API。它是替代 XMLHttpRequest 用来发送网络请求的非常新的 API。由于目前大多数浏览器原生还不支持它,建议你使用 isomorphic-fetch 库:

在底层,它在浏览器端使用 whatwg-fetch polyfill,在服务器端使用 node-fetch,所以如果当你把应用改成同构时,并不需要改变 API 请求。

评论和共享

React Touch 事件

兼容性

经测试,兼容 ie9+ 主流浏览器

手机单击触发事件

Chrome

onTouchStart -> onTouchEnd -> onMouseDown -> onMouseUp

Firefox

onTouchStart -> onTouchEnd -> onMouseDown -> onMouseMove -> onMouseUp

手机触摸移动触发事件

Chrome

onTouchStart -> onTouchMove -> onTouchEnd

Firefox

onTouchStart -> onTouchMove -> onTouchEnd -> onMouseDown -> onMouseMove -> onMouseUp

获取坐标方式

事件 方法
onMouseDown const {pageX, pageY} = e;
onMouseMove const {pageX, pageY} = e;
onMouseUp const {pageX, pageY} = e;
onTouchStart const {pageX,pageY} = e.touches[0];
onTouchMove const {pageX, pageY} = e.touches[0];
onTouchEnd const {clientX, clientY} = e.changedTouches[0];

示例

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
import React,{ Component } from 'react';
import styles from './App.css';

class App extends Component {
constructor(props) {
super(props);
this.checked = false;

this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);

this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
}

handleTouchStart(e){
const {pageX,pageY} = e.touches[0];
console.log("handleTouchStart", pageX, pageY);
}

handleTouchMove(e){
const {pageX, pageY} = e.touches[0];
console.log("handleTouchMove", pageX, pageY);
}

handleTouchEnd(e){
const {clientX, clientY} = e.changedTouches[0];
console.log("handleTouchEnd", clientX, clientY);
}

handleMouseDown(e){
this.checked = true;
console.log("handleMouseDown", e.pageX, e.pageY);
}

handleMouseMove(e){
if(this.checked)
console.log("handleMouseMove", e.pageX, e.pageY);
}

handleMouseUp(e){
this.checked = false;
console.log("handleMouseUp", e.pageX, e.pageY);
}

render(){
return (
<div className={styles.app}>
<h2>Hello, Nice</h2>
<div className={styles.touchBox}
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}

onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
/>
</div>
)
}
}

export default App;

评论和共享

CSS-Modules

CSS Modules 用法

局部作用域

CSS 的规则是全局的,产生局部作用域的唯一方法,就是使用一个独一无二的 class 的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。
下面是一个 React 组件 App.js

1
2
3
4
5
6
7
8
9
10
import React from 'react';
import style from './App.css';

export default () => {
return (
<h1 className={style.title}>
Hello World
</h1>
);
};

将样式文件 App.css 输入到 style 对象,然后引用 style.title 代表一个 class

1
2
3
.title {
color: red;
}

构建工具会将 style.title 编译成一个哈希字符串。

1
2
3
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
Hello World
</h1>

app.css 也会同时被编译。

1
2
3
._3zyde4l1yATCOkgn-DBWEL {
color: red;
}

这样一来,这个类名就变成独一无二了,只对 App 组件有效。

CSS Modules 提供各种插件,支持不同的构建工具。本文使用的是 Webpackcss-loader 插件,因为它对 CSS Modules 的支持最好,而且很容易使用。

示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = {
entry: __dirname + '/index.js',
output: {
publicPath: '/',
filename: './bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'stage-0', 'react']
}
},
{
test: /\.css$/,
loader: "style-loader!css-loader?modules"
},
]
}
};

但个人认为使用下面这种方法更加好些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: [
path.resolve(__dirname, "/node_modules/")
],
query: {
presets: ['es2015','react']
}
},
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{loader: "css-loader",options: {modules: true}}
]
}
]
},

上面代码中,关键的一行是 style-loader!css-loader?modules ,它在 css-loader 后面加了一个查询参数 modules ,表示打开 CSS Modules 功能。

全局作用域

CSS Modules 允许使用 :global(.className) 的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串。

App.css加入一个全局class

1
2
3
4
5
6
7
.title {
color: red;
}

:global(.title) {
color: green;
}

CSS Modules 还提供一种显式的局部作用域语法 :local(.className),等同于.className,所以上面的App.css也可以写成下面这样。

1
2
3
4
5
6
7
:local(.title) {
color: red;
}

:global(.title) {
color: green;
}

定制哈希类名

css-loader默认的哈希算法是[hash:base64],这会将.title编译成._3zyde4l1yATCOkgn-DBWEL这样的字符串。
webpack.config.js里面可以定制哈希字符串的格式。

1
2
3
4
5
6
7
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
}

Class 组合

在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为”组合”(”composition”)。

App.css 中,让 .title 继承 .className

1
2
3
4
5
6
7
8
.className {
background-color: blue;
}

.title {
composes: className;
color: red;
}

输入其他模块

选择器也可以继承其他 CSS 文件里面的规则。

App.css 可以继承 another.css 里面的规则。

1
2
3
4
.title {
composes: className from './another.css';
color: red;
}

输入变量

CSS Modules 支持使用变量,不过需要安装 postcss-loaderpostcss-modules-values

1
npm install postcss-loader postcss-modules-values --save-dev

1
2
3
4
{
test: /\.css$/,
loader: "style-loader!css-loader?modules!postcss-loader"
}

接着,在colors.css里面定义变量

1
2
3
@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;

App.css 可以引用这些变量。

1
2
3
4
5
6
7
@value colors: "./colors.css";
@value blue, red, green from colors;

.title {
color: red;
background-color: blue;
}

评论和共享

CSS-Modules

根据浏览器CSS渲染原理写出高性能的CSS代码

注意:CSS 引擎查找样式表,对每条规则都按从右到左的顺序去匹配。

例子

示例1

1
#nav li {}

CSS选择符是从右到左进行匹配的,浏览器必须遍历页面上每个li元素并确定其父元素的id是否为nav。

示例2

1
*{}

这种效率是差到极点的做法,因为 * 通配符将匹配所有元素,所以浏览器必须去遍历每一个元素,这样的计算次数可能是上万次

示例3

1
ul#nav{} ul.nav{}

在页面中一个指定的ID只能对应一个元素,所以没有必要添加额外的限定符,而且这会使它更低效。同时也不要用具体的标签限定类选择符,而是要根据实际的情况对类名进行扩展。例如把 ul.nav 改成 .main_nav 更好。

优化方案

  1. 避免使用通配规则。如 *{} 计算次数惊人!只对需要用到的元素进行选择
  2. 尽量少的去对标签进行选择,而是用 class。如: #nav li{} ,可以为 li 加上 nav_item 的类名,如下选择 .nav_item{}

  3. 不要去用标签限定ID或者类选择符。如:ul#nav ,应该简化为 #nav

  4. 尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。

  5. 考虑继承。了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则

评论和共享

CSS-Modules

css-loader & style-loader 的区别

css-loader以一个字符串的形式读入一个css文件。并返回带导入的CSS,并通过webpack的require功能解决url(…):

style-loader会使用这些样式,并在包含这些样式的页面的元素中创建一个

评论和共享

  • 第 1 页 共 1 页
作者的图片

Archie Shi

Nothing to say


Front-End Development Engineer