react搭建項目步驟,基于react-app搭建react-router+redux項目

 2023-12-25 阅读 28 评论 0

摘要:前言 總括:?本文采用react+redux+react-router+less+es6+webpack,以實現一個簡易備忘錄(todolist)為例盡可能全面的講述使用react全家桶實現一個完整應用的過程。 代碼地址:React全家桶實現一個簡易備忘錄原文博客地址:React全家桶

前言

總括:?本文采用react+redux+react-router+less+es6+webpack,以實現一個簡易備忘錄(todolist)為例盡可能全面的講述使用react全家桶實現一個完整應用的過程。

  • 代碼地址:React全家桶實現一個簡易備忘錄
  • 原文博客地址:React全家桶實現一個簡易備忘錄
  • 知乎專欄&&簡書專題:前端進擊者(知乎)&&前端進擊者(簡書)
  • 博主博客地址:Damonare的個人博客

人生不失意,焉能暴己知。

技術說明

技術架構:本備忘錄使用react+react-router+redux+less+ES6+webpack實現;

頁面UI參照:TodoList官網實現;

在線演示地址:Damonare的備忘錄;

功能說明

  • 支持回車添加新事項;
  • 支持刪除事項(點擊X符號);
  • 支持狀態轉換具體包括:
    • 新建事項->正在進行(點擊checkbox選項)
    • 正在進行->已完成(點擊文字內容本身)
    • 正在進行->新建事項(點擊checkbox選項)
    • 已完成->正在進行(點擊文字本身)
  • 支持判斷輸入空字符,過長字符(20個漢字以內);
  • 支持搜索;
  • 支持本地化存儲;
  • 支持狀態的展開隱藏(點擊標題)
  • 兼容手機端(iPhone6及以上)
  • 支持路由切換

1. React淺談

1.1 組件化

當談到React的時候不能避免的會提到組件化思想。React剛開始想解決的問題是UI層面的問題,view層面的問題。后來越滾越大,從最早的UI引擎變成了一整套前后端通吃的 Web App 解決方案。一個完整的頁面是由大大小小的組件堆疊而成,組件套組件組成了用戶所能看到的完整的頁面。

1.2 JSX語法糖

? 使用React,不一定非要使用JSX語法,可以使用原生的JS進行開發。但是React作者強烈建議我們使用JSX,如下:

 

簡單明了!!!具體的JSX語法不多說了,學習更多戳這:JSX?

1.3 Virtual DOM

虛擬DOM的概念并不是FB首創卻在FB的手上大火了起來。

頁面對應了一個DOM樹,在傳統頁面的開發模式中,每次需要更新頁面時,都需要對DOM進行更新,DOM操作十分昂貴,為減少對于真實DOM的操作,誕生了Virtual DOM的概念,也就是用javascript把真實的DOM樹描述了一遍,使用的也就是我們剛剛說過的JSX語法。

對比如下:

?

每次數據更新之后,重新計算Virtual DOM,并和上一次的Virtual DOM對比,對發生的變化進行批量更新。React也提供了shouldComponentUpdate生命周期回調,來減少數據變化后不必要的Virtual DOM對比過程,提升了性能。

Virtual DOM雖然渲染方式比傳統的DOM操作要好一些,但并不明顯,因為對比DOM節點也是需要計算的,最大的好處在于可以很方便的和其它平臺集成,比如react-native就是基于Virtual DOM渲染出原生控件。具體渲染出的是Web DOM還是Android控件或是iOS控件就由平臺決定了。這一點上讓我們多平臺的編碼相當方便。

1.4 函數式編程

? 過去編程方式主要是以命令式編程為主。電腦生硬的執行指令,給電腦下達命令,電腦去執行,現在主要的編程語言(比如:Java,C,C++等)都是由命令式編程構建起來的。

? 而函數式編程就不一樣了,這是模仿我們人類的思維方式發明出來的。例如:操作某個數組的每一個元素然后返回一個新數組,如果是計算機的思考方式,會這樣想:創建一個新數組=>遍歷舊數組=>給新數組賦值。如果是人類的思考方式,會這樣想:創建一個數組方法,作用在舊數組上,返回新數組。這樣此方法可以被重復利用。而這就是函數式編程了。

1.5 數據流

在React中,數據是單向流動的,是從上向下的方向,即從父組件到子組件的方向。
state和props是其中重要的概念,如果頂層組件初始化props,那么React會向下遍歷整顆組件樹,重新渲染相關的子組件。其中state表示的是每個組件中內部的的狀態,這些狀態只在組件內部改變。
把組件看成是一個函數,那么他接受props作為參數,內部由state作為函數的內部參數,返回一個虛擬dom的實現。
state
state是組件內部的狀態,當組件內部使用內置的setState方法,該組件會重新進行渲染。
注意一下,setState方法是一個異步的方法,在一個生命周期中的所有setState方法會合并操作。
props
props是React中用來讓組件之間相互聯系的一種機制,props的傳遞過程對React是非常直觀的,React的單向數據流主要的流動管道就是props,props本身是不可變的,當我們試圖改變props的原始值,React會報類型錯誤的警告。組件的props一定是來源于默認指定的屬性或者是從父組件傳入的。
React為props提供了默認配置,通過defaultProps靜態變量的方式來定義,當組件被調用的時候,默認值保證渲染后始終有值。
在React中有一個內置的props--children,代表的是子組件的集合,根據子組件的數量,this.props.children的數據類型而且不一致,當沒有子組件的時候為undefined,當有一個的時候為object,當有多個的時候為array。
propTypes
propTypes是用來規范props的類型和必須的狀態,如果組件定義了propTypes,那么在開發環境下會對props進行檢查,在生產環境下是不會進行檢查的。

?

1.6 React主要就下圖之中的這些內容,掌握了這個圖就算入門了。

?

2. React-router

官方例子:react-router-tutorial。

完事以后可以再看一下阮一峰老師的教程,主要是對一些API的講解:React Router 使用教程。

3. Redux?

3.1 簡介

隨著 JavaScript 單頁應用開發日趨復雜,JavaScript 需要管理比任何時候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標簽,是否顯示加載動效或者分頁器等等。如果一個 model 的變化會引起另一個 model 變化,那么當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。亂!

這時候Redux就強勢登場了,現在你可以把React的model看作是一個個的子民,每一個子民都有自己的一個狀態,紛紛擾擾,各自維護著自己狀態,太亂了,我們需要一個King來領導大家,我們就可以把Redux看作是這個King。網羅所有的組件組成一個國家,掌控著一切子民的狀態!防止有人叛亂生事!

這個時候就把組件分成了兩種:容器組件(King或是路由)和展示組件(子民)。

  • 容器組件:即redux或是router,起到了維護狀態,出發action的作用,其實就是King高高在上下達指令。
  • 展示組件:不維護狀態,所有的狀態由容器組件通過props傳給他,所有操作通過回調完成
    ?展示組件容器組件
    作用描述如何展現(骨架、樣式)描述如何運行(數據獲取、狀態更新)
    直接使用 Redux
    數據來源props監聽 Redux state
    數據修改從 props 調用回調函數向 Redux 派發 actions
    調用方式手動通常由 React Redux 生成

Redux三大部分:store,action,reducer。相當于King的直系下屬。

那么也可以看出Redux只是一個狀態管理方案,完全可以單獨拿出來使用,這個King不僅僅可以是React的,去Angular,Ember那里也是可以做King的。在React中維系King和組件關系的庫叫做?react-redux

, 它主要有提供兩個東西:Provider?和connect,具體使用文后說明。

提供幾個Redux的學習地址:官方教程-中文版,Redux 入門教程(一):基本用法

3.2 Store

Store 就是保存數據的地方,它實際上是一個Object tree。整個應用只能有一個 Store。這個Store可以看做是King的首相,掌控一切子民(組件)的活動(state)。

Redux 提供createStore這個函數,用來生成 Store。

import { createStore } from 'redux';
const store = createStore(func);

createStore接受一個函數作為參數,返回一個Store對象(首相誕生記)

我們來看一下Store(首相)的職責:

  • 維持應用的 state;
  • 提供?getState()?方法獲取 state;
  • 提供?dispatch(action)?方法更新 state;
  • 通過?subscribe(listener)?注冊監聽器;
  • 通過?subscribe(listener)?返回的函數注銷監聽器。

3.3 action

State 的變化,會導致 View 的變化。但是,用戶接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導致的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。即store的數據變化來自于用戶操作。action就是一個通知,它可以看作是首相下面的郵遞員,通知子民(組件)改變狀態。它是 store 數據的唯一來源。一般來說會通過?store.dispatch()?將 action 傳到 store。

Action 是一個對象。其中的type屬性是必須的,表示 Action 的名稱。

const action = {type: 'ADD_TODO',payload: 'Learn Redux'
};

?

Action創建函數

Action 創建函數?就是生成 action 的方法。“action” 和 “action 創建函數” 這兩個概念很容易混在一起,使用時最好注意區分。

在 Redux 中的 action 創建函數只是簡單的返回一個 action:

function addTodo(text) {return {type: ADD_TODO,text}
}

這樣做將使 action 創建函數更容易被移植和測試。

?

3.4 reducer

Action?只是描述了有事情發生了這一事實,并沒有指明應用如何更新 state。而這正是 reducer 要做的事情。也就是郵遞員(action)只負責通知,具體你(組件)如何去做,他不負責,這事情只能是你們村長(reducer)告訴你如何去做。

專業解釋:?Store 收到 Action 以后,必須給出一個新的 State,這樣 View 才會發生變化。這種 State 的計算過程就叫做 Reducer。

Reducer 是一個函數,它接受 Action 和當前 State 作為參數,返回一個新的 State。

const reducer = function (state, action) {// ...return new_state;
};

3.5 數據流

下圖展示了Redux帶來的變化。

?

Redux 應用中數據的生命周期遵循下面 4 個步驟:

  • 調用?store.dispatch(action)
  • Redux store 調用傳入的 reducer 函數。
  • 根 reducer 應該把多個子 reducer 輸出合并成一個單一的 state 樹。
  • Redux store 保存了根 reducer 返回的完整 state 樹

工作流程圖如下:

?component -> action -> store -> reducer -> state 的單向數據流,概括的說就是:React組件里面獲取了數據之后(比如ajax請求),然后創建一個action通知store我有這個想改變state的意圖,然后reducers(一個action可能對應多個reducer,可以理解為action為訂閱的主題,可能有多個訂閱者)來處理這個意圖并且返回新的state,接下來store會收集到所有的reducer的state,最后更新state。

3.6 Connect

這里需要再強調一下:Redux 和 React 之間沒有關系。Redux 支持 React、Angular、Ember、jQuery 甚至純 JavaScript。

盡管如此,Redux 還是和?React?和?Deku?這類框架搭配起來用最好,因為這類框架允許你以 state 函數的形式來描述界面,Redux 通過 action 的形式來發起 state 變化。

Redux 默認并不包含?React 綁定庫,需要單獨安裝。?

npm install --save react-redux

?

?React-Redux?提供connect方法,用于從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。?

import { connect } from 'react-redux';
const TodoList = connect()(Memos);

?

?上面代碼中Memos是個UI組件,TodoList就是由 React-Redux 通過connect方法自動生成的容器組件。

而只是純粹的這樣把Memos包裹起來毫無意義,完整的connect方法這樣使用:?

import { connect } from 'react-redux'
const TodoList = connect(mapStateToProps
)(Memos)

?

?上面代碼中,connect方法接受兩個參數:mapStateToPropsmapDispatchToProps。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),后者負責輸出邏輯,即將用戶對 UI 組件的操作映射成 Action。

?

3.7 Provider

?這個Provider 其實是一個中間件,它是為了解決讓容器組件拿到King的指令(state對象)而存在的。

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(<Provider store={store}><App /></Provider>,document.getElementById('root')
)

?

4.實戰備忘錄

講解之前可以先看一下github上的代碼,你可以clone下來學習,也可以在線給我提issue,歡迎戳這:React全家桶實現簡易備忘錄

?4.1目錄結構

?

接下來,我們只關注app目錄就好了。

4.2入口文件??

app/main.jsx?

import React from 'react';
import ReactDOM from 'react-dom';
import { Route, IndexRoute, hashHistory, Router } from 'react-router';
import { Provider } from 'react-redux';
import App from './container/App';
import AllMemosRoute from './routes/AllMemosRoute';
import TodoRoute from './routes/TodoRoute';
import DoingRoute from './routes/DoingRoute';
import DoneRoute from './routes/DoneRoute';
import configureStore from './stores';
import './main.less';const store = configureStore();
ReactDOM.render(<Provider store={store}><Router history={hashHistory}><Route path="/" component={App}><IndexRoute component={AllMemosRoute} /><Route path="/todo" component={TodoRoute} /><Route path="/doing" component={DoingRoute} /><Route path="/done" component={DoneRoute} /></Route></Router></Provider>,document.getElementById('root')
);

?

這里我們從react-redux中獲取到 Provider 組件,我們把它渲染到應用的最外層。
他需要一個屬性 store ,他把這個 store 放在context里,給Router(connect)用。

4.3 Store

app/store/index.jsx?

import { createStore } from 'redux';
import reducer from '../reducers';
export default function configureStore(initialState) {const store = createStore(reducer, initialState);if (module.hot) {// Enable Webpack hot module replacement for reducersmodule.hot.accept('../reducers', () => {const nextReducer = require('../reducers');store.replaceReducer(nextReducer);});}return store;
}

?

4.4 Action 創建函數和常量

app/action/index.jsx

'use strict';
/** @author Damonare 2016-12-10* @version 1.0.0* action 類型*/
export const Add_Todo = 'Add_Todo';
export const Change_Todo_To_Doing = 'Change_Todo_To_Doing';
export const Change_Doing_To_Done = 'Change_Doing_To_Done';
export const Change_Done_To_Doing = 'Change_Done_To_Doing';
export const Change_Doing_To_Todo = 'Change_Doing_To_Todo';
export const Search='Search';
export const Delete_Todo='Delete_Todo';
/** action 創建函數* @method  addTodo添加新事項* @param  {String} text 添加事項的內容*/
export function addTodo(text) {return {type: Add_Todo,text}
}
/** @method  search 查找事項* @param  {String} text 查找事項的內容*/
export function search(text) {return {type: Search,text}
}
/** @method  changeTodoToDoing 狀態由todo轉為doing* @param  {Number} index 需要改變狀態的事項的下標*/
export function changeTodoToDoing(index) {return {type: Change_Todo_To_Doing,index}
}
/** @method  changeDoneToDoing 狀態由done轉為doing* @param  {Number} index 需要改變狀態的事項的下標*/
export function changeDoneToDoing(index) {return {type: Change_Done_To_Doing,index}
}
/** @method  changeDoingToTodo 狀態由doing轉為todo* @param  {Number} index 需要改變狀態的事項的下標*/
export function changeDoingToTodo(index) {return {type: Change_Doing_To_Todo,index}
}
/** @method  changeDoingToDone 狀態由doing轉為done* @param  {Number} index 需要改變狀態的事項的下標*/
export function changeDoingToDone(index) {return {type: Change_Doing_To_Done,index}
}
/** @method  deleteTodo 刪除事項* @param  {Number} index 需要刪除的事項的下標*/
export function deleteTodo(index) {return {type: Delete_Todo,index}
}

?

?在聲明每一個返回 action 函數的時候,我們需要在頭部聲明這個 action 的 type,以便好組織管理。
每個函數都會返回一個 action 對象,所以在 組件里面調用?

text =>dispatch(addTodo(text))

?

?就是調用dispatch(action)?。

?

4.5 Reducers?

app/reducers/index.jsx??

import { combineReducers } from 'redux';
import todolist from './todos';
// import visibilityFilter from './visibilityFilter';

const reducer = combineReducers({todolist,
});export default reducer;?

app/reducers/todos.jsx

?

import {ADD_TODO,DELETE_TODO,CHANGE_TODO_TO_DOING,CHANGE_DOING_TO_DONE,CHANGE_DOING_TO_TODO,CAHNGE_DONE_TO_DOING,SEARCH,
} from '../actions';let todos;
(() => {if (localStorage.todos) {todos = JSON.parse(localStorage.todos);} else {todos = [];}
})();
const todolist = (state = todos, action) => {switch (action.type) {/**  添加新的事項*  并進行本地化存儲*  使用ES6展開運算符鏈接新事項和舊事項*  JSON.stringify進行對象深拷貝*/case ADD_TODO:return [...state, {todo: action.text,istodo: true,doing: false,done: false}];/** 將todo轉為doing狀態,注意action.index的類型轉換*/case CHANGE_TODO_TO_DOING:localStorage.setItem('todos', JSON.stringify([...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: true,done: false},...state.slice(parseInt(action.index) + 1)]));return [...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: true,done: false},...state.slice(parseInt(action.index) + 1)];/** 將doing轉為done狀態*/case CHANGE_DOING_TO_DONE:localStorage.setItem('todos', JSON.stringify([...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: false,done: true},...state.slice(parseInt(action.index) + 1)]));return [...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: false,done: true},...state.slice(parseInt(action.index) + 1)];/** 將done轉為doing狀態*/case CAHNGE_DONE_TO_DOING:localStorage.setItem('todos', JSON.stringify([...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: true,done: false},...state.slice(parseInt(action.index) + 1)]));return [...state.slice(0, action.index),{todo:state[action.index].todo,istodo: false,doing: true,done: false},...state.slice(parseInt(action.index) + 1)];/** 將doing轉為todo狀態*/case CHANGE_DOING_TO_TODO:localStorage.setItem('todos', JSON.stringify([...state.slice(0, action.index),{todo:state[action.index].todo,istodo: true,doing: false,done: false},...state.slice(parseInt(action.index) + 1)]));return [...state.slice(0, action.index),{todo:state[action.index].todo,istodo: true,doing: false,done: false},...state.slice(parseInt(action.index) + 1)];/** 刪除某個事項*/case DELETE_TODO:localStorage.setItem('todos', JSON.stringify([...state.slice(0, action.index),...state.slice(parseInt(action.index) + 1)]));return [...state.slice(0, action.index),...state.slice(parseInt(action.index) + 1)];/** 搜索*/case SEARCH:let text = action.text;let reg = eval("/"+text+"/gi");return state.filter(item=> item.todo.match(reg));default:return state;}
}
export default todolist;

?

轉載于:https://www.cnblogs.com/tianyamoon/p/11235299.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/194857.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息