TodoMVC是一個示例項目,它使用目前流行的不一樣JavaScript框架的來實現同一個Demo,來幫助你熟悉和選擇最合適的前端框架。css
怎樣快速學習和了解一門JavaScript框架或學習使用新框架的特性? 個人經驗是作一個TodoMVC應用吧html
TodoMVC 應用功能點vue
TodoMVC
是一個功能和UI都十分完備的教學示例,實乃學習框架之必備🌰react
Redux
是遵循 Flux
模式的一種實現,是一個狀態管理庫,適用於 React
,Angular
,VueJs
等框架或庫,而不是侷限於某一特定UI庫。git
Redux中文文檔github
Redux 核心概念npm
+----------+
| | +-----------+ sliceState,action +------------+
| Action |dispacth(action)| +--------------------------------> |
| +----------------> store | | Reducers |
| Creators | | <--------------------------------+ |
| | +-----+-----+ (state, action) => state +------------+
+-----^----+ |
| |
| |subscribe
| |
| |
| |
| +--------+ |
+--------------+ View <---+
+--------+
複製代碼
更新View的方式redux
dispatch
觸發 action
action
攜帶方法名 type
, 和數據 payload
到 store
(期間可能進行異步數據處理)store
接收到 type
和 payload
交給相應的 reducer
reducer
找到對應的方法更新 state
Store、Action 與 Reducerbash
Store
是整個 Redux 應用的狀態容器,是一個對象Action
也是一個對象,代表事件,須要有 type 字段Reducer
是一個函數,會根據不一樣 Action 來決定返回不一樣的數據新增Todo
刪除Todo
complete
的數據修改Todo
查詢Todo
all
active
complete
能夠過濾list,而且高亮自動能切換active
和 complete
樣式TodoMVC
模塊npm install todomvc-common todomvc-app-css
import 'todomvc-common/base.css';
import 'todomvc-app-css/index.css';
複製代碼
直接使用vue版TodoMVC模板,去掉vue相關代碼,保留結構
使用 todomvc
UI, 能夠免去樣式和dom結構編寫,讓咱們專一於使用JS框架快速實現業務邏輯
頁面基本結構
// App.tsx
const Input: SFC<any> = () => {...}
const TodoItem: SFC<ITodoItemProps> = {...}
const TodoList: SFC<any> = () => {...}
const Footer: SFC<any> = () => {...}
function TodoAPP() {
return (
<section className="todoapp"> <header className="header"> <h1>todos</h1> <Input /> </header> <TodoList /> <Footer /> </section>
);
}
複製代碼
應用全局狀態
const initialState: IAPPState = {
todos: todoStore.list,
newTodo: '',
editTodo: '',
visibility: ShowType.ALL,
};
複製代碼
這裏狀態的劃分爲全局狀態和局部狀態,局部狀態由組件內部管理,後面會說到,因此TodoMVC 的全局狀態基本就是這4個就夠了,在實際項目應用中,全局狀態儘可能少,要作到每一個狀態都是全局必須的,避免在全局存放過多的狀態,可能會引發組件沒必要要的更新。
事件方法
export enum ActionType {
// 新增Todo
CREATE = 'create',
// 更新Todo
UPDATE = 'update',
// 刪除Todo
DELETE = 'delete',
// 刪除狀態是已完成的Tdo
REMOVE_COMPLETED = 'removeCompleted',
// 設置當前編輯的Todo
EDIT_SET = 'setEdit',
// 改變當前顯示類型
CHANGE_SHOW_TYPE = 'changeShowType',
// 更新當前編輯的Todo
UPDATE_EDIT_TODO = 'updateEditTodo',
// 所有切換爲 完成/未完成
TOGGLE_ALL = 'toggleAll',
}
複製代碼
使用 createContext
建立 Context
組件
使用 useReducer 管理複雜的 state
useReducer
接收一個形如 (state, action) => newState
的 reducer
,並返回當前的 state
以及與其配套的 dispatch
方法
使用 <Context.Provider>
作爲父組件的組件,將在 state
更新時,觸發更新
// Store.tsx
const initialState: IAPPState = {
todos: todoStore.list,
newTodo: '',
editTodo: '',
visibility: ShowType.ALL,
};
const reducer = (state: IAPPState, action: IAction): IAPPState => {
const { type, payload } = action;
return methods[type] ? methods[type](state, payload) : state;
};
const Context = React.createContext({
state: initialState,
dispatch: (() => 0) as React.Dispatch<IAction>,
});
const Provider: SFC<any> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return <Context.Provider value={{ state, dispatch}}>{children}</Context.Provider>;
};
export { Provider, Context };
複製代碼
上面的 methods
是具體更新 state
的方法對象
Provider
的子組件能夠從 Context
中獲取全局的 state
與 dispatch
// App.tsx
import { Provider, Context } from './Store'
function TodoAPP() {
return (
<Provider>
<section className="todoapp">
...
</section>
</Provider>
);
}
複製代碼
使用 useContext 接收 Store
中的 Context
對象,並返回 state
和 dispatch
當組件上層最近的 <Context.Provider>
更新時,該 hook 會觸發組件更新,並使用最新的 state
調用了 useContext
的組件總會在 context
值變化時從新渲染
使用 useState 建立組件內部狀態 title
, 該狀態在組件內部使用或銷燬,並不影響全局state, 接收一個初始狀態的初始值和更新狀態的函數 changeTitle
使用 dispatch
觸發一個 action
操做,建立一個 todo
const Input: SFC<any> = () => {
const { state, dispatch } = useContext(Context);
// 內部狀態 title
const [title, changeTitle] = useState<string>('');
// 輸入時更新 title
function onChange(e: ChangeEvent<HTMLInputElement>) {
changeTitle(e.target.value.trim());
}
// 回車新增一條Todo
function onKeyDown(e: KeyboardEvent<HTMLInputElement>) {
// 新增
dispatch({
type: ActionType.CREATE,
payload: { id: utils.uuid(), title, completed: false },
});
// 清空
changeTitle('');
}
return (
<input
className="new-todo"
autoFocus
autoComplete="off"
placeholder="What needs to be done?"
value={title}
onChange={onChange}
onKeyDown={onKeyDown}
/>
);
};
複製代碼
editing
是屬於每個 TodoItem
的內部狀態
使用 useRef 來返回一個可變的 ref 引用對象,其 .current
屬性被初始化爲傳入的參數,經常使用於訪問 DOM對象
,這裏用來使用編輯輸入框獲取焦點
useEffect 告訴組件須要在渲染後執行某些操做。React
會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM
更新以後調用它, 這裏傳入第二個參數 [editing]
作爲依賴,以優化 effect
執行次數,只有當 editing
改變時,才執行
const TodoItem: SFC<ITodoItemProps> = ({ completed, title, index }) => {
const [editing, changeEditing] = useState<boolean>(false);
const iptRef = useRef(null);
const { state, dispatch } = useContext(Context);
const { editTodo } = state;
// DOM更新,獲取焦點
useEffect(() => (editing && iptRef.current.focus()), [editing]);
// 雙擊編輯
function onDoubleClick() {...}
// 失焦,更新Todo
function onEditBlur() {...}
// 回車,更新Todo
function onEditEnter(e: KeyboardEvent<HTMLInputElement>) {...}
// 更新當前編輯的Todo
function onEditChange(e: ChangeEvent<HTMLInputElement>) {...}
// 切換完成狀態
function onToggleComplete(e: ChangeEvent<HTMLInputElement>) {...}
// 刪除
function onDestroy() {
return (
<li>
...
</li>
)
}
複製代碼
使用 useMemo 獲取相似Vue中的 計算屬性,避免在每次渲染時都進行高開銷的計算,傳入 todos
, 當 todos
更新時才從新計算
const Footer: SFC<any> = () => {
const { state, dispatch } = useContext(Context);
const { todos, visibility } = state;
// 計算屬性:正在進行中的數量、已完成的數量、提示文字
const { activedNum, completedNum, activeTodoWord } = useMemo(() => {...})
// 改變顯示類型
function onChangeShowType(type: ShowType) {...}
// 清空已完成的Todo
function onClearCompleted() {...}
return (
<footer className="footer">
...
</footer>
)
}
複製代碼
const TodoList: SFC<any> = () => {
const { state, dispatch } = useContext(Context);
const { todos, visibility } = state;
const activedNum = useMemo(() => utils.filterTodos(todos, ShowType.ACTIVE).length, [todos]);
const todoList = useMemo(() =>utils.filterTodos(todos, visibility), [todos, visibility]);
function onToggleAll(e: ChangeEvent<HTMLInputElement>) {...}
return (
<section className="main">
<input
id="toggle-all"
className="toggle-all"
type="checkbox"
onChange={onToggleAll}
checked={activedNum === 0}
/>
<label htmlFor="toggle-all">Mark all as complete</label>
<ul className="todo-list">
{todoList.map((v, i) => {
return <TodoItem key={v.id} completed={v.completed} title={v.title} index={i} />;
})}
</ul>
</section>
)
}
複製代碼
爲了防止文章過長,並無粘貼全部代碼,主要說明 React
中的一些 hooks
在 TodoMVC
中的一些實際應用場景,以便於咱們更容易更快速的理解和掌握hooks的用法
在咱們公司的實際應用中,並無使用 redux
來管理應用狀態,而是使用 mobx
,在實際開發中我更傾向局部狀態與全局狀態分而治之,在後臺管理系統中,全局的狀態並很少,無非就是一些用戶信息、菜單狀態和數據、消息等等,通常以頁面劃分 store
,複雜的組件有本身的 store
, store
中包含了全部狀態和操做方法。
Redux 的缺點
actionTypes
、actions
、reducers
dispatch
須要引用 actions
或要記憶 Action Type
等問題優化 dispatch
在上面的 hooks 實現的僞 redux
中, dispatch
方法使用仍然比較繁瑣,能夠優化下面兩點
import { ActionsTypes } from './types'
import { Context } from './store'
// 如今的調用方式
const {state, dispatch} = useContext(Context)
dispatch({
type: ActionTypes.TOGGLE_ALL,
payload: { completed },
});
// 優化後的調用方式
const {state, actions} = useContext(Context)
actions.toggleAll({ completed })
複製代碼
這裏的優化實現主要在 Provider
的實現中, 只要把 Actions
方法掛載到 Context
中就能夠
const Provider: SFC<any> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// 生成 Actions
type IActions = { [name in ActionType]?: (data?: Record<string, any>) => void };
let actions: IActions = {};
for (let i in ActionType) {
actions[ActionType[i] as ActionType] = data =>
dispatch({ type: ActionType[i] as ActionType, payload: data });
}
// Context 掛載 state、dispatch、actions
return <Context.Provider value={{ state, dispatch, actions }}>{children}</Context.Provider>;
};
複製代碼
Vue
相較於 React
有一個優勢就是 Vue
的包比 React
要小,在 React
中使用 setState
來管理應用狀態,相比於 Vue
,代碼寫法上稍顯繁瑣,引用第三方的庫如 Redux
或 mobx
無疑又會增長React的體積,hooks
的出現給了咱們以但願,在一些相對簡單的應用中徹底能夠代替 mobx
或 redux
。可是一樣也要當心使用,沒有了生命週期的控制,使用不當也容易形成組件重複屢次渲染產生性能瓶頸,因此在使用 hooks
時,要多思考怎樣使用更合理,怎樣纔不會產生性能問題。