React項目實踐(一)從框架配置講起

只是一篇流水帳的學習紀錄
技術棧: react + redux + react-router-dom + antd-mobile
面向:webappjavascript

----正文分界線-----前端

安裝

npx create-react-app react-juejin
cd react-juejin
yarn add antd-mobile rc-form react-loadable react-redux
yarn add -D @babel/plugin-proposal-decorators @rematch/core babel-plugin-import customize-cra less less-loader react-app-rewired
複製代碼

基本結構

目錄拆分邏輯

在開發中,可能會常常遇到兩個頁面或者區域展現的文檔結構和樣式是相同的,但數據源和交互不一樣。該狀況下如何左到最大化的功能複用呢?——將展現部分抽離,數據和交互部分分開java

  • assets: 圖片/第三方樣式文件等
  • components: 放置公共展現組件。單純負責將獲取的Props傳來的數據進行展現
    • component: 咱們能夠將與組件視圖相關的樣式腳本放置在每一個componet文件夾,入口文件爲index.js
  • containers: 容器組件,主要負責數據的獲取,業務相關的交互等內容。
  • layouts: 在前端頁面中一般有能夠複用的頁面佈局,好比導航,底部菜單是固定的。咱們能夠經過一個寫一個高階組件來處理佈局
  • routes: 路由配置相關
  • store:全局狀態管理
    • models:定義每一個模塊的狀態管理,包括state, reducers, effects
  • utils: 工具類
  • services:放置與數據交互相關的api

重寫webpack配置

之間試過使用eject將原有配置拆開來寫,此次使用react-app-rewired,具體用法可見官網,下面是根據antd-mobile定製主題重寫的一個config-overrides.js,支持裝飾器和less語法react

const {
  override,
  fixBabelImports,
  addWebpackAlias,
  addLessLoader,
  addDecoratorsLegacy
} = require('customize-cra')
const path = require('path')
const theme = require('./package.json').theme
module.exports = {
  webpack: override(
    addWebpackAlias({
      '@components': path.resolve(__dirname, 'src/components'),
      '@assets': path.resolve(__dirname, 'src/assets'),
      '@layouts': path.resolve(__dirname, 'src/layouts'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@store': path.resolve(__dirname, 'src/store'),
      '@containers': path.resolve(__dirname, 'src/containers'),
      '@services': path.resolve(__dirname, 'src/services')
    }),
    fixBabelImports('import', {
      libraryName: 'antd-mobile',
      libraryDirectory: 'lib',
      style: true,
      legacy: true
    }),
    addLessLoader({
      javascriptEnabled: true,
      modifyVars: theme
    }),
    addDecoratorsLegacy({
      legacy: true
    })
  )
}
複製代碼

咱們能夠在package.json定製咱們項目的主題顏色 從新配置項目啓動命令webpack

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
複製代碼

路由配置

咱們使用Loadable動態載入路由git

const Home = Loadable({
  loader: () => import('@containers/Home'),
  loading: () => <HomeLoading />
})
//...
//配置路由信息
const routes = [
  {path: '/', exact: true, component: Home},
  {path: '/home', exact: true, component: Home},
  {path: '/post/:id', component: Post},
  {
      path: '/profile',
      component: Profile,
      routes: [{path: '/profile/notification',component: Notification}
        //...
      ]
    }
    //...
]
export default routes
複製代碼

根據文檔編寫可嵌套渲染的組件github

import React from "react"
import {Route} from "react-router-dom";
export function RouteWithSubRoutes(route) {
    return (
      <Route
        path={route.path}
        render={props => (
          // pass the sub-routes down to keep nesting
          <route.component {...props} routes={route.routes} />
        )}
      />
    );
  }
複製代碼

渲染路由(index.js)web

import React from 'react'
import ReactDOM from 'react-dom'
import routes from './routes'
import {BrowserRouter as Router, Switch} from 'react-router-dom'
import {RouteWithSubRoutes} from './routes/RouteWithSubRoutes'
const RouterConfig = () => (
  <Router> <Switch> {routes.map((route, i) => ( <RouteWithSubRoutes key={i} {...route} /> ))} </Switch> </Router> ) ReactDOM.render(<RouterConfig />, document.getElementById('root')) 複製代碼

咱們能夠在containers中編寫簡單的頁面組件,測試路由配置是否成功(containers/Home/index.js)json

import React, {Component} from 'react'
class HomeContainer extends Component {
  render() {
    return (
      <div> HOME </div>
    )
  }
}
export default HomeContainer
複製代碼

路由配置完畢,到這一步你已經能訪問不一樣的頁面了redux

使用HOC編寫一個佈局組件

這是我模仿掘金app展現的一個頁面,咱們能夠從中抽取出:① 導航欄的佈局是固定在頁面頂部的;② 左側有一個箭頭能夠返回原來頁面。那麼一個簡單的佈局以下:

import React, {Component} from 'react'
import {NavBar, Icon} from 'antd-mobile'
const withNavBarBasicLayout = title => {
  return WrappedComponent => {
    return class extends Component {
      render() {
        return (
          <div>
            <NavBar
              mode="dark"
              icon={<Icon type="left" />}
              onLeftClick={this.goBack}>
              {title}
            </NavBar>
            <WrappedComponent {...this.props} />
          </div>
        )
      }
      goBack = () => {
        this.props.history.goBack()
      }
    }
  }
}
export default withNavBarBasicLayout
複製代碼

咱們在須要佈局的container頁面,使用裝飾器語法指定佈局

@withNavBarBasicLayout('首頁特別展現')
class TabPicker extends Component {
//...
}
複製代碼

這樣一個簡單的佈局已經完成了,咱們能夠編寫多個佈局樣式,好比常見的三欄佈局,只要在頁面指定便可

全局狀態管理

這個折騰了挺久,官網上的examples通常都是將actions,reducers,saga中間件等拆分來寫。按這個配置,寫一個簡單的狀態變化須要在多個文件夾中切換。後面看到了@rematch/core,真是個神器,咱們可使用這個編寫一個精簡的相似dva風格的狀態管理。將state,reducers,effects做爲一個models管理。仍是以掘金app的首頁展現爲例

首頁有一個tablist展現咱們選定的關注內容,tabList的數據是多個路由頁面共享的數據,所以咱們考慮使用store管理。這裏咱們考慮將標籤展現存儲在本地localStorage中。編寫一個簡單的model(store/models/home.js)

export default {
  namespace: 'home',
  state: {
    tabList: [
      {title: '前端', show: true},
      {title: '設計', show: true},
      {title: '後端', show: true},
      {title: '人工智能', show: true},
      {title: '運維', show: true},
      {title: 'Android', show: true},
      {title: 'iOS', show: true},
      {title: '產品', show: true},
      {title: '工具資源', show: true}
    ]
  },
  reducers: {
    //resetTabList
    resetTabList(state, {tabList}) {
      return {
        ...state,
        tabList: tabList || state.tabList
      }
    }
  },
  effects:{
    async getTabListAsync(playload, state) {
      let tabList = await loadData('tabList')
      this.resetTabList({tabList})
    },
    async resetTabListAsync(playload, state) {
      await saveData('tabList', playload.tabList)
      this.resetTabList(playload)
    }
  }
}
複製代碼

配置models出口頁面(models/index.js)

import home from './home'
export default {
  home
}
複製代碼

註冊store(store/index.js)

import { init } from '@rematch/core';
import models from './models'
const store = init({
    models
})
export default store;
複製代碼

在根目錄的index.js提供一個根Provider提供全部路由頁面能夠訪問的store

//新增
import store from './store'
import {Provider} from 'react-redux'
//修改
const RouterConfig = () => (
  <Router> <Provider store={store}> <Switch> {routes.map((route, i) => ( <RouteWithSubRoutes key={i} {...route} /> ))} </Switch> </Provider> </Router> ) 複製代碼

對每一個頁面,使用connect關聯,在首頁進行初始化dispatch

import {connect} from 'react-redux'
const mapState = state => ({
  tabList: state.home.tabList
})
const mapDispatch = ({home: {resetTabListAsync}}) => ({
  resetTabListAsync: (tabList) => resetTabListAsync({tabList: tabList})
})
@connect(mapState,mapDispatch)
@withTabBarBasicLayout('home')
class HomeContainer extends Component {
  static propTypes = {
    tabList: PropTypes.array.isRequired
  }
  componentWillMount() {
    //能夠在這裏初始化全局數據
    this.props.getTabListAsync()
  }
  //...
}
複製代碼

咱們還能夠在標籤管理頁,修改數據, 這樣兩個頁面的數據是一致的。

this.setState({tabList: items},() => {
    this.props.resetTabListAsync(this.state.tabList)
})
複製代碼

大功告成啦!

基本的項目框架完成了,後面還要折騰登陸鑑權和數據處理部分,暫時寫到這,項目放在github上 地址


補充:項目用到的icon能夠在應用商店直接下載apk解壓,篩選圖片格式文件就能夠啦;一些圖片能夠用阿里雲的iconfont引入

相關文章
相關標籤/搜索