接下來 3 篇文章的目標是以 React + Redux + Webpack 的技術來完成 To-Do List 功能與 PWA,而內容會假設讀者已經熟悉 React, Redux 及 Webpack,會把重點放在如何實作,不會逐步講解基礎觀念。
本篇為第 2 篇: 建立檔案架構與功能
放置網站的圖片資源
入口檔案
針對檔案架構個別說明如下:
App.js 代表整個 Application,屬於應用程式最外層的元件,可以把固定的版面放這裡,此範例放置 Header 元件
import React, { Component } from 'react';
import Header from '../Component/Header.jsx';
export default class App extends Component {
render() {
return (
<div>
{ /* 應用程式最外層的元件 */ }
{ /* 把固定的版面放這裡 */ }
<Header />
{ this.props.children }
</div>
);
}
}
Home.js 用來放置主要內容,這裡放 Main 元件
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Main from '../Container/Main.jsx';
class Home extends Component {
render() {
return (
<div>
<Main />
</div>
);
}
}
export default (Home);
Main.jsx 包括 InputContainer 和 TodoListContainer.jsx
import React, { Component } from 'react';
import Input from './InputContainer.jsx';
import TodoListContainer from './TodoListContainer.jsx';
export default class Main extends Component {
render() {
return (
<div>
<Input />
<TodoListContainer />
</div>
);
}
}
InputContainer
的程式碼傳入 Input
元件所需的 action creator 為 addTodoList,用來處理『新增待辦事項』的方法
import { connect } from 'react-redux';
import Input from '../Component/Input.jsx';
import { addTodoList } from '../Reducers/todolist.js';
export default connect(null, {
addTodoList
})(Input);
TodoListContainer
的程式碼傳入 TodoList
元件所需的 store(state.todolist) 和 action creator,store 包括 id、是否完成狀態、待辦事項文字; action creator 則有三個方法,分別是 getTodoList (取得待辦事項)、toggleTodoList (修改待辦事項)、delTodoList (刪除待辦事項)
import TodoList from '../Component/TodoList.jsx';
import { connect } from 'react-redux';
import { getTodoList, toggleTodoList, delTodoList } from '../Reducers/todolist.js';
export default connect(state => ({
todos: state.todolist
}), {
getTodoList,
toggleTodoList,
delTodoList
})(TodoList);
放置 LOGO 和顯示未完成的個數
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Header extends Component {
render () {
const { unfinishedCount } = this.props;
return (
<header className="header">
<img
className="logo"
src="./assets/images/logo_todo.png"
alt=""
/>
<div className="unfinished">
<span className="count">{ unfinishedCount }</span>
<span>個未完成</span>
</div>
</header>
);
}
}
export default connect(state => {
return {
unfinishedCount: state.todolist
.filter((item)=>!item.isComplete).length
}
})(Header);
輸入 To-Do List 內容
import React, { Component } from 'react';
export default class Input extends Component {
constructor (props) {
super(props);
this.state = {
value: ''
}
}
render() {
const { addTodoList } = this.props;
return (
<div id="todoInput" className="input-content">
<img
className="add"
src="./assets/images/ic_add.png"
alt=""
/>
<input
className="input"
placeholder="What need to be done?"
value={ this.state.value }
type="text"
onChange={
(event) => {
this.setState({
value: event.target.value
});
}
}
onKeyDown={
(event) => {
if (event.keyCode === 13) {
console.log('Enter');
addTodoList(this.state.value);
this.setState({value: ''})
}
}
}
/>
</div>
);
}
}
顯示 To-Do List 內容(ul)
import React, { Component } from 'react';
import TodoListItem from './TodoListItem.jsx';
export default class TodoList extends Component {
componentDidMount() {
this.props.getTodoList();
}
render() {
const { todos, toggleTodoList, delTodoList } = this.props;
return (
<ul id="todoList">
{
todos.map((item, index) => {
return (
<TodoListItem
key={`todolist_${index}`}
id={ item.id }
desc={ item.desc }
isComplete={ item.isComplete }
toggleTodoList={ toggleTodoList }
delTodoList={ delTodoList }
/>
)
})
}
</ul>
);
}
}
顯示 To-Do List 項目(li)
import React, { Component } from 'react';
export default class TodoListItem extends Component {
constructor(props) {
super(props);
this.modifyList = this.modifyList.bind(this);
}
modifyList() {
const { isComplete, id, desc } = this.props;
this.props.toggleTodoList({
id,
desc,
isComplete: !isComplete
});
}
render() {
const { isComplete, id, desc, delTodoList } = this.props;
return (
<li className="list">
<a
className={ isComplete ? 'finish' : 'unfinished' }
onClick={ () =>{this.modifyList();} }
>
</a>
<p
className="desc"
onClick={ () =>{this.modifyList();} }
>
{ desc }
</p>
<a
className="del"
onClick={ () => { delTodoList(id); } }
>
</a>
</li>
);
}
}
Reducers 則用來存放資料,搭配 action type、action creator 處理資料,再用 Reducers/index.js
整合 Reducers,此範例只有一個 Reducer (todolist.js)。
先前文章已使用 Vanilla JS 完成 To-Do List 功能,複習一下之前實作 Vanilla JS 的相關文章:
這次則是要改成 React + Redux 實作功能。
todolist 的 Reducer 方法如下,用來處理 todolist 資料:
export default function todolist (state = initialState, action) {
switch (action.type) {
case RECEIVE_TODO:
return action.data;
case ADD_TODO:
state.push(action.payload);
return [...state];
case TOGGLE_TODO:
state.forEach((item, index) => {
if (item.id === action.payload.id) {
state[index] = action.payload;
}
})
return [...state];
case DEL_TODO:
return state.filter(item => item.id !== action.id)
default:
return state;
}
}
action creator 如下:
export function receiveTodoList (data) {
return {
type: RECEIVE_TODO,
data: data
};
}
export function getTodoList() {
return function(dispatch) {
return axios.get(`${URL}todolist`)
.then(res => {
dispatch(receiveTodoList(res.data));
});
}
}
export function receiveAddTodoList (payload) {
return {
type: ADD_TODO,
payload: payload
};
}
export function addTodoList (value) {
return function(dispatch) {
return axios({
method: 'post',
url: `${URL}todolist`,
headers: {
'Content-Type': 'application/json'
},
data: {
isComplete: false,
desc: value
}
})
.then(res => {
dispatch(receiveAddTodoList(res.data));
})
.catch(error => { console.log(error) })
}
}
export function toggleTodoList (payload) {
return function(dispatch) {
return axios({
method: 'put',
url: `${URL}todolist/${payload.id}`,
headers: {
'Content-Type': 'application/json'
},
data: payload
})
.then(res => {
dispatch({type: TOGGLE_TODO, payload: payload});
})
}
}
export function delTodoList (id) {
return function(dispatch) {
return axios({
method: 'delete',
url: `${URL}todolist/${id}`,
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
dispatch({type: DEL_TODO, id: id});
})
}
}
補充說明: 元件為什麼要拆分為
Container
和Component
?
可以參考 Container Components 和 Presentational and Container Components 這 2 篇文章,當然目前示範的 To-Do List 是小範例,但如果專案越來越龐大,為了重用性、可維護性等原因,建議拆分 Container
和 Component
,由 Container
處理資料,讓 Component
專心顯示內容。
此篇文章針對『建立檔案架構與功能』做說明,從規劃專案的架構開始,新增各種元件去顯示畫面,接著建立 Redux 的 Store 、 Reducer 和 Action creator 完成待辦事項的功能,而下一篇將會介紹『實作 PWA』。
本人小小筆記,如有錯誤或需要改進的部分,歡迎給予回饋。
我將會用最快的速度修正,m(_ _)m。謝謝