一、建立一個文件,而且初始化javascript
npm init -y tsc --init touch .gitignore
二、安裝依賴包css
# 基礎的包 npm install react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom # 安裝關於webpack的包 npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D # 安裝處理ts的 npm i typescript ts-loader source-map-loader -D # 安裝redux相關的 npm install redux react-redux @types/react-redux redux-thunk redux-logger @types/redux-logger redux-promise @types/redux-promise # 安裝路由與redux鏈接的庫 npm install connected-react-router # 處理樣式的 npm install style-loader css-loader less-loader less -D # 處理前綴的 npm install autoprefixer postcss-loader -D # 處理圖片地址類的 npm install url-loader file-loader lib-flexible -D # px轉換rem(看須要安裝,僅用於手機網站) npm install px2rem-loader # ajax請求庫 npm install axios # react輪播圖組件庫(看須要安裝) npm install react-swipe @types/react-swipe # react動畫庫(看須要安裝) npm install react-transition-group @types/react-transition-group
三、修改tsconfig.json
文件html
{ "compilerOptions": { "outDir": "./dist", /*指定輸出的目錄*/ "sourceMap": true, /*把ts文件編譯成js文件的時候,同時生成對應的sourceMap文件*/ "noImplicitAny": true, /*若是爲true的話,ts編譯器沒法推斷出類型的時候,依然會編譯成js文件,可是會*/ "module": "commonjs", /*規範*/ "target": "es5", /*轉換爲es5*/ "jsx": "react", "esModuleInterop": true, "baseUrl": ".", // 查找非相對路徑的模塊的起始位置 "paths": { // 定義別名 "@/*": [ "src/*" ] } }, "include": [ /*須要編譯的文件*/ "./src/**/*" ] }
四、配置webpack
文件前端
項目下建立一個
webpack.config.js
的文件java
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', }, devtool: 'source-map', resolve: { // 定義別名 alias: { '@': path.resolve(__dirname, 'src'), '~': path.resolve(__dirname, 'node_modules') }, // 當你加載一個文件的時候,沒有指定擴展名的時候,會自動尋找哪些擴展名 extensions: ['.ts', '.tsx', '.js', '.json'] }, module: { rules: [ { test: /\.(j|t)sx?/, loader: 'ts-loader', options: { transpileOnly: true, //只編譯不檢查 compilerOptions: { module: 'es2015' } } }, { test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 0 } }, //{ // loader: 'postcss-loader', // options: { // plugins: [require('autoprefixer')] // } //} ] }, { test: /\.less$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 0 } }, //{ // loader: 'postcss-loader', // options: { // plugins: [require('autoprefixer')] // } //}, // 若是是手機端就要配置 // { // loader: 'px2remote-loader', // options: { // remUnit: 75, // 基礎尺寸 // remPrecesion: 8 // 精確到多少位 // } // }, 'less-loader', ] }, // 處理圖片類 { test: /\.(jpg|png|gif|svg|jpeg)$/, use: ['url-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', }), // 熱更新 new webpack.HotModuleReplacementPlugin(), ], devServer: { hot: true, contentBase: path.join(__dirname, 'dist'), open: false, port: 3000, historyApiFallback: { // browserHistory的時候,刷新會報404. 自動重定向到index.html index: './index.html' } } }
五、建立一個src
目錄,而且建立兩個文件index.html
和index.tsx
文件node
六、在package.json
中配置啓動命令react
{ ... "scripts": { "dev": "webpack-dev-server", "build": "webpack" }, }
七、在src/index.tsx
的文件中寫上react
代碼webpack
import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( <div>我是使用react建立的</div>, document.getElementById('root') )
八、在src/index.html
中要寫一個基本的容器ios
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>手動搭建react項目</title> </head> <body> <div id="root"></div> </body> </html>
九、若是你是手機網站就要加上適配的(看項目需求來寫的)git
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>手動搭建react項目</title> </head> <body> <script> const docEl = document.documentElement; function setRemUtils() { docEl.style.fontSize = docEl.clientWidth / 10 + 'px'; } setRemUtils(); window.addEventListener('resize', setRemUtils, false); </script> <div id="root"></div> </body> </html>
十、啓動項目
react
結合redux
一塊兒使用一、在src
下建立一個store
的文件夾
二、大致的目錄結構
➜ store tree . ├── action-types.ts # 定義常量 ├── actions # 一個組件對應一個action ├── index.ts └── reducers # 一個組件對應一個reducer └── index.ts 2 directories, 3 files
三、在store/index.ts
文件中
import { createStore, applyMiddleware } from 'redux'; import logger from 'redux-logger'; import promise from 'redux-promise'; import thunk from 'redux-thunk'; import rootReducer from './reducers'; const store = applyMiddleware( promise, thunk, logger)(createStore)(rootReducer); // 掛載到window上,方便查看,能夠不寫 (<any>window).store = store; export default store;
四、store/reducers/index.ts
文件用來整合各個組件中的reducer
import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux' // 各個組件的reducer的狀態類型,能夠單獨到一個文件中 export interface CombinedState { } const reducers: ReducersMapObject<CombinedState, AnyAction> = { // 各個組件的reducer }; const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers); export default rootReducers;
五、在src/index.tsx
中使用store
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import store from './store'; ReactDOM.render( <Provider store={store}> <div>我是使用react建立的</div> </Provider> , document.getElementById('root') )
store
是否配置成功一、在action-types.ts
中定義兩個常量
export const ADD_COUNT = 'ADD_COUNT'; export const MINUS_COUNT = 'MINUS_COUNT';
二、建立一個文件src/store.counter.ts
的reducer
文件
import { AnyAction } from 'redux'; import * as types from './../action-types'; export interface Counter { count: number } const initState: Counter = { count: 0, }; export default function (state: Counter = initState, action: AnyAction) { switch (action.type) { case types.ADD_COUNT: return { ...state, count: state.count + 1 }; case types.MINUS_COUNT: return { ...state, count: state.count - 1 }; default: return state; } }
三、在src/store/reducers/index.ts
中引入組件的reducer
... import counter, { Counter } from './counter'; // 各個組件的reducer的狀態類型 export interface CombinedState { counter: Counter, } const reducers: ReducersMapObject<CombinedState, AnyAction> = { // 各個組件的reducer counter, }; ...
四、定義actions
文件
import * as types from './../action-types'; export default { // 增長的方法 // payload表示觸發該函數傳遞的參數 addCount(payload: number) { console.log('我是傳遞進來的 payload', payload); return { type: types.ADD_COUNT, payload, } }, // 減小的方法 minusCount(payload: number) { return { type: types.MINUS_COUNT, payload, } } }
五、建立一個組件components/Counter.tsx
import React, { PropsWithChildren } from 'react'; import { connect } from 'react-redux'; import action from '@/store/actions/counter'; type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>; // 隨便定義一個,能夠不寫 type State = { [propsName: string]: any } class Counter extends React.Component<Props, State> { render() { console.log(this.props); return ( <> <button onClick={this.props.minusCount}>-</button> {this.props.count} {/* 須要傳遞payload參數就要這樣寫 */} <button onClick={() => this.props.addCount(10)}>+</button> </> ) } } const mapStateToProps = (state: any) => (state.counter) export default connect( mapStateToProps, action, )(Counter);
六、在src/index.tsx
中使用該組件而且測試是否成功
Redux DevTools
觀察狀態的變化一、谷歌瀏覽器安裝插件Redux DevTools
(不能上應用市場的要本身想辦法)
三、在src/store/index.ts
中使用
... import { composeWithDevTools } from 'redux-devtools-extension'; // 採用另一種寫法 const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(promise, thunk, logger))); ...
// 開發環境使用工具及日誌中間件 const enhancers = process.env.NODE_ENV === "development" ? composeWithDevTools( applyMiddleware(promise, thunk, logger) ) : applyMiddleware(promise, thunk); const store = createStore(rootReducer, enhancers);
四、運行效果圖
store
的類型約束一、在項目建立一個文件夾src/typings
二、建立一個文件typings/counter.ts
約束剛剛的counter.tsx
組件的數據
export interface CounterState { count: number }
三、建立一個文件typings/state.ts
的文件
import { CounterState } from '.'; // 各個組件的reducer的狀態類型 export interface CombinedState { counter: CounterState, } export interface CounterPayload { count: number, }
四、精簡store/reducers/counter.ts
代碼
import { AnyAction } from 'redux'; import * as types from './../action-types'; import { CounterState } from '@/typings'; const initState: CounterState = { count: 0, }; export default function (state: CounterState = initState, action: AnyAction) { ... }
五、精簡store/reducers/index.ts
代碼
import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux' import counter from './counter'; import { CombinedState } from '@/typings'; const reducers: ReducersMapObject<CombinedState, AnyAction> = { // 各個組件的reducer counter, }; const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers); export default rootReducers;
六、優化store/actions/counter.ts
文件,讓類型約束payload
import * as types from './../action-types'; import { CounterPayload } from '@/typings'; export default { // 增長的方法 // payload表示觸發該函數傳遞的參數 addCount(payload: CounterPayload) { console.log('我是傳遞進來的 payload', payload); return { type: types.ADD_COUNT, payload, } }, // 減小的方法 minusCount() { // 若是須要傳遞參數的時候就加上payload,不傳遞的時候就不要加 return { type: types.MINUS_COUNT, } } }
七、在組件中也可使用抽取出的類型約束
import React, { PropsWithChildren } from 'react'; import { connect } from 'react-redux'; import action from '@/store/actions/counter'; import { CombinedState } from '@/typings'; type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>; // 隨便定義一個,能夠不寫 type State = { [propsName: string]: any } class Counter extends React.Component<Props, State> { render() { ... } } // 使用類型約束 const mapStateToProps = (state: CombinedState) => (state.counter) export default connect( mapStateToProps, action, )(Counter)
redux-thunk
和axios
請求的使用一、大致的流程
action
action
中接收到事件後,發起ajax
請求ajax
請求數據中,根據條件判斷是否派發dispatch
(注意這個地方返回一個函數,函數中使用當即執行函數)reducer
中處理剛剛action
派發出來的types
將數據存放到state
中state
中的數據二、先在項目的src
目錄下建立一個utils
的文件夾,咱們先對axios
簡單的封裝下
// 實際業務中可能更加具體,如今只是簡單的封裝 import axios, { AxiosRequestConfig } from 'axios'; axios.defaults.baseURL = 'http://test.dancebox.cn/api/v1'; axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'; axios.interceptors.request.use((config: AxiosRequestConfig) => { let access_token = sessionStorage.getItem('access_token'); if (access_token) config.headers['Authorization'] = `Bearer ${access_token}`; return config; }, (error: any) => Promise.reject(error)); //response攔截器裏把AxiosResponse=>AxiosResponse.data axios.interceptors.response.use(response => response.data, error => Promise.reject(error)); export { axios };
三、在項目的src
目錄下建立一個api
專門來存放數據請求的方法
import https from '@/utils/https'; // 定義請求接口 export const activityList = () => { return https.get('/front/activity'); }
四、在reducers/index.ts
定義兩個數據類型
... export type StoreDispatch = Dispatch; export type StoreGetState = () => CombinedState; export default rootReducers;
五、在actions/counter.ts
文件中請求活動數據
import * as types from './../action-types'; import { CounterPayload } from '@/typings'; import { activityList } from '@/api'; import { StoreDispatch, StoreGetState } from '../reducers'; export default { ... // 使用ajax請求返回數據,由於咱們使用redux-thunk,那麼reducer能夠返回一個函數 activityList() { return function (dispatch: StoreDispatch, getState: StoreGetState) { // 這裏要使用async就要使用當即執行函數 (async function () { const response: { [propsName: string]: any } = await activityList(); const { code, message, result } = response; if (Object.is(code, 0)) { dispatch({ type: types.GET_ACTIVITY_DATA, payload: result, }) } else { console.log('獲取數據失敗', message); } })(); } } }
六、在src/typings/counter.ts
文件中新增活動的
export interface CounterState { count: number, activityListData: any[], // 新增存放活動列表的數據 }
七、處理reducers/counter.ts
將actions
中dispath
的數據存到state
中
import { AnyAction } from 'redux'; import * as types from './../action-types'; import { CounterState } from '@/typings'; const initState: CounterState = { count: 0, activityListData: [], }; export default function (state: CounterState = initState, action: AnyAction) { switch (action.type) { ... case types.GET_ACTIVITY_DATA: // 獲取到請求的數據,放到store中 console.log(action.payload); return { ...state, activityListData: action.payload.data } default: return state; } }
八、在組件中使用
import React, { PropsWithChildren } from 'react'; import { connect } from 'react-redux'; import action from '@/store/actions/counter'; import { CombinedState } from '@/typings'; type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>; // 隨便定義一個,能夠不寫 type State = { [propsName: string]: any } class Counter extends React.Component<Props, State> { render() { console.log(this.props); return ( <> <button onClick={this.props.activityList}>獲取活動數據</button> <ul> { this.props.activityListData.map(item => { return ( <li key={item.id}>{item.title}</li> ) }) } </ul> </> ) } } const mapStateToProps = (state: CombinedState) => (state.counter) export default connect( mapStateToProps, action, )(Counter)
antd
前端UI
庫一、安裝依賴包
npm install antd npm install ts-import-plugin
二、修改webpack.config.js
的配置
const tsImportPluginFactory = require('ts-import-plugin'); module.exports = { ... module: { rules: [ { test: /\.(j|t)sx?/, loader: 'ts-loader', options: { transpileOnly: true, //只編譯不檢查 getCustomTransformers: () => ({ // 獲取或者說定義自定義的轉換器 before: [tsImportPluginFactory({ 'libraryName': 'antd', // 對哪一個模塊進行按需加載 'libraryDirectory': 'es', // 按需加載的模塊,若是實現按需加載,必須是ES Modules 'style': 'css' // 自動引入它對應的CSS })] }), compilerOptions: { module: 'es2015' } } }, ] }, ... }
三、在組件中使用
import React, { PropsWithChildren } from 'react'; import { connect } from 'react-redux'; import { Button } from 'antd'; import action from '@/store/actions/counter'; import { CombinedState } from '@/typings'; type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>; // 隨便定義一個,能夠不寫 type State = { [propsName: string]: any } class Counter extends React.Component<Props, State> { render() { console.log(this.props); return ( <> ... <Button type="primary" onClick={this.props.activityList}>獲取活動數據</Button> <ul> { this.props.activityListData.map(item => { return ( <li key={item.id}>{item.title}</li> ) }) } </ul> </> ) } } const mapStateToProps = (state: CombinedState) => (state.counter) export default connect( mapStateToProps, action, )(Counter)
四、配置antd
的中文包(在src/index.tsx
文件中)
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { ConfigProvider } from 'antd' import zh_CN from 'antd/lib/locale-provider/zh_CN'; import store from './store'; import Counter from '@/components/Counter'; // 全局樣式 import './assets/style/common.less'; ReactDOM.render( <Provider store={store}> <ConfigProvider locale={zh_CN}> <Counter /> </ConfigProvider> </Provider>, document.getElementById('root') )