再來我們要美化版面,之前有提過在React中使用style的方式,今天要介紹使用class的方式。分成兩個部分,第一個部分是webpack如何打包CSS檔,第二個部分是在React中指定class name的方式。
之前在Day10,我們有介紹一個plugin extract-text-webpack-plugin ,它會將我們程式中有require("main.css")的CSS(或是使用ES6 import的CSS),都統一搬到一個CSS中。
先安裝相關plugins:
npm install css-loader style-loader extract-text-webpack-plugin --save-dev
再到webpack中設定,style和css的loader,和使用plugins:
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
}
]
},
plugins: [
// 這邊設定bundle CSS後的檔名
new ExtractTextPlugin("styles.css")
]
}
建立一個css資料夾,放入我們編寫的main.css,使用require或import,把css載入:
require("../css/main.css");
// or
import '../css/main.css';
當我們執行 npm run prod 的時候,就會發現build的資料夾中,已經建立一個style.css檔案。而當我們執行 npm run dev 的時候,因為使用index.html,所以我們需要加上css style到html上。
<link rel="stylesheet" href="styles.css">
這樣一來,就可以發現已經成功使用webpack整合css囉!
classnames是一個方便javascript管理class name的package,可以有條件的設定class name,配合React剛剛好!
如果我們要設定多個class name給一個元件,通常我們會這樣寫:
render() {
return (<div className="class1 class2 class3"></div>);
}
可是如果要根據元件state或是props來改變這些class狀態,就會顯得很複雜,你可能這樣需要寫:
render() {
let classStr = "class1";
if (this.props.isCompleted) {
classStr += "class2";
} else {
classStr += "class3";
}
return (<div className={classStr}></div>);
}
為了避免這樣複雜的寫法,我們可以使用classnames來管理class,當然,首先也是要先install package:
npm install classnames --save
基本的用法,如下
import classNames from 'classnames';
classNames('foo', 'bar'); // => 'foo bar'
classNames是一個function,可以傳入string或是object,如果傳入 'foo' ,表示是 {foo: true} 的縮寫。當key的value是falsy(相當於false),表示是不顯示的class,有時候配合判別式可以決定某個state或props情況下不顯示class。
以下是官方說明的範例,看完應該就會秒懂:
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// 傳入多組string或object
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// 只要是falsy value,都會被忽略不顯示
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
// 當傳入陣列時,每一個值都會扁平化
var arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'
而配合ES6的Template literals,可以動態組合key的名稱:
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true }); // => 'btn-primary'
改寫上面提到複雜的寫法,就會是這樣:
import classNames from 'classnames';
// ...
render() {
const classStr = classNames({
'class1': true,
'class2': this.props.isCompleted,
'class3': !this.props.isCompleted
});
return (<div className={classStr}></div>);
}
這樣管理class就變得很簡單明瞭,這邊也把之前todos的範例,加上styles,在css資料夾中新增一個style.css,並加上相關的className,這邊列出最主要有用到classnames的部分,也把TodoItem.js裡面原本使用的style都改成classnames。
render() {
const { todo, idx, deleteTask, completeTask } = this.props;
const taskClass = classNames({
task: true,
'task-completed': todo.isCompleted
});
if (this.state.isEditing) {
return (
<tr>
<td><input type="text" data-idx={idx} defaultValue={todo.task} ref="editInput" className="edit" /></td>
<td>
<button onClick={this._onSaveClick}>Save</button>
<button onClick={this._onCancelClick}>Cancel</button>
</td>
</tr>
);
}
return (
<tr>
<td>
<span
className={taskClass}
onClick={() => completeTask(idx)}
>
{todo.task}
</span>
</td>
<td>
<button onClick={this._onEditClick}>Edit</button>
<button onClick={() => deleteTask(idx)}>Delete</button>
</td>
</tr>
);
}
加上好看樣式的部分也完成囉!今天的檔案放在Git上。
當我們執行 npm run prod 的時候,就會發現build的資料夾中,已經建立一個style.css檔案。而當我們執行 npm run dev 的時候,因為使用inex.html,所以我們需要加上css style到html上。
inex.html
應該是 index.html
已修正~感謝你!