现在我们来进入项目基础搭建的环节。

本节代码对应 GitHub 分支: chapter2

仓库传送门 (opens new window)

初始项目的搭建主要分为三个部分进行:

  1. 路由的配置和应用部分
  2. 公共组件的开发
  3. redux 的 store 创建和引入

现在让我们开始吧。

# 一、路由配置

# 路由文件编写

要开发一个复杂应用的时候,首先要做的并不是上来就开始写具体功能,要想清楚整个应用的结构,从路由开始入手编写是一个比较好的方式,也建议大家拿到别人的项目的时候从路由开始着手,可以很好的整理我们的思路。

应用的骨架其实非常简单,顶部有固定的内容及 tab 栏,下面对应不同的功能组件。

首先安装依赖。

npm install react-router react-router-dom react-router-config --save

现在我们在 routes 目录下新建 index.js 文件,利用 react-router-config 来对路由进行配置。

//routes/index.js
import React from 'react';
import { Redirect } from "react-router-dom";
import Home from '../application/Home';
import Recommend from '../application/Recommend';
import Singers from '../application/Singers';
import Rank from '../application/Rank';

export default [
  {
    path: "/",
    component: Home,
    routes: [
      {
        path: "/",
        exact: true,
        render: () => (
          <Redirect to={"/recommend"}/>
        )
      },
      {
        path: "/recommend",
        component: Recommend
      },
      {
        path: "/singers",
        component: Singers
      },
      {
        path: "/rank",
        component: Rank
      }
    ]
  }
]

Home 组件对应公共组件,下面的推荐组件、歌手列表组件和排行榜组件为具体的功能组件。

为了让路由文件生效,必须在 App 根组件下面导入路由配置,现在在 App.js 中:

import React from 'react';
import { GlobalStyle } from  './style';
import { renderRoutes } from 'react-router-config';//renderRoutes 读取路由配置转化为 Route 标签
import { IconStyle } from './assets/iconfont/iconfont';
import routes from './routes/index.js';
import { HashRouter } from 'react-router-dom';

function App () {
  return (
    <HashRouter>
      <GlobalStyle></GlobalStyle>
      <IconStyle></IconStyle>
      { renderRoutes (routes) }
    </HashRouter>
  )
}

export default App;

# 新建组件文件

现在你的项目应该是无法启动的,因为这些组件你都没有定义和引入。

现在, 在 application 目录下,新建 Home 文件夹,然后新建 index.js 文件,

//src/appliction/Home/index.js
import React from 'react';

function Home (props) {
  return (
    <div>Home</div>
  )
}

export default React.memo (Home);

然后类似的,创建 Recommend、Singers 和 Rank 组件。

启动项目,打开页面,你可以看到 "home" 已经显示到屏幕,但是这还不够,我们需要展示下面的功能组件,但是你在地址后面加上 /recommend,却并没有显示 Recommend 组件相应的内容,因为 renderRoutes 这个方法只渲染一层路由,之前 Home 处于数组第一层,后面的功能组件在第二层,当然不能正常渲染啦。其实要解决这个问题也非常简单,只需在 Home 中再次调用 renderRoutes 即可。

//src/appliction/Home/index.js
import React from 'react';
import { renderRoutes } from "react-router-config";

function Home (props) {
  const { route } = props;

  return (
    <div>
      <div>Home</div>
      { renderRoutes (route.routes) }
    </div>
  )
}

export default React.memo (Home);

好,现在你可以访问 /recommend 路由,应该可以看到 Recommend 中的内容。同理,现在也可以正常访问其它的路由啦。

# 二、公共组件开发

路由折腾清楚后,我们来着手开发项目的第一个组件: Home 组件。

# 全局样式准备

现在要真正开始写样式了,为了统一风格,需要一些全局样式配置,在 assets 目录下新建 global-style.js, 内容如下:

// 扩大可点击区域
const extendClick = () => {
  return `
    position: relative;
    &:before {
      content: '';
      position: absolute;
      top: -10px; bottom: -10px; left: -10px; right: -10px;
    };
  `
}
// 一行文字溢出部分用... 代替
const noWrap = () => {
  return `
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
  `
}

export default {
  'theme-color': '#d44439',
  'theme-color-shadow': 'rgba (212, 68, 57, .5)',
  'font-color-light': '#f1f1f1',
  'font-color-desc': '#2E3030',
  'font-color-desc-v2': '#bba8a8',// 略淡
  'font-size-ss': '10px',
  'font-size-s': '12px',
  'font-size-m': '14px',
  'font-size-l': '16px',
  'font-size-ll': '18px',
  "border-color": '#e4e4e4',
  'background-color': '#f2f3f4',
  'background-color-shadow': 'rgba (0, 0, 0, 0.3)',
  'highlight-background-color': '#fff',
  extendClick,
  noWrap
}

# 顶部栏开发

首先,在 Home 目录下新建 style.js,创建 CSS 样式组件

import styled from'styled-components';
import style from '../../assets/global-style';

export const Top = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 5px 10px;
  background: ${style ["theme-color"]};
  &>span {
    line-height: 40px;
    color: #f1f1f1;
    font-size: 20px;
    &.iconfont {
      font-size: 25px;
    }
  }
`

很简单的布局和样式,就不过多解释了。接下来在 Home 组件应用这些样式,

//src/appliction/Home/index.js
import React from 'react';
import { renderRoutes } from "react-router-config";
import { Top } from './style';

function Home (props) {
  const { route } = props;

  return (
    <div>
      <Top>
        <span className="iconfont menu">&#xe65c;</span>
        <span className="title">WebApp</span>
        <span className="iconfont search">&#xe62b;</span>
      </Top>
      { renderRoutes (route.routes) }
    </div>
  )
}

export default React.memo (Home);

接着来编写上面的 tab 栏,先定义样式:

export const Tab = styled.div`
  height: 44px;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  background: ${style ["theme-color"]};
  a {
    flex: 1;
    padding: 2px 0;
    font-size: 14px;
    color: #e4e4e4;
    &.selected {
      span {
        padding: 3px 0;
        font-weight: 700;
        color: #f1f1f1;
        border-bottom: 2px solid #f1f1f1;
      }
    }
  }
`
export const TabItem = styled.div`
  height: 100%;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
`

在 Home 组件中使用:

import React from 'react';
import { renderRoutes } from "react-router-config";
import {
  Top,
  Tab,
  TabItem,
} from './style';
import { NavLink } from 'react-router-dom';// 利用 NavLink 组件进行路由跳转

function Home (props){
  const { route } = props;

  return (
    <div>
      <Top>
        <span className="iconfont menu">&#xe65c;</span>
        <span className="title">Web App</span>
        <span className="iconfont search">&#xe62b;</span>
      </Top>
      <Tab>
        <NavLink to="/recommend" activeClassName="selected"><TabItem><span > 推荐 </span></TabItem></NavLink>
        <NavLink to="/singers" activeClassName="selected"><TabItem><span > 歌手 </span></TabItem></NavLink>
        <NavLink to="/rank" activeClassName="selected"><TabItem><span > 排行榜 </span></TabItem></NavLink>
      </Tab>
      { renderRoutes (route.routes) }
    </div>
  );
}

export default React.memo (Home);

打开页面,现在一个像样的 WebApp 头部就出来了,并且点击不同的 tab 会显示不同的功能组件。

image-20210215180607313

# 三、redux 准备

本项目开发的一大核心理念就是用 Redux 这一成熟的状态管理库实现单一数据源。因此,在后面的具体功能开发之前,有必要准备一些关于 Redux 的工作。

# 安装相应依赖

npm install redux redux-thunk redux-immutable react-redux immutable --save

其中 redux-immutable 大家可能比较陌生,因为项目中需要用到 immutable.js 中的数据结构,所以合并不同模块 reducer 的时候需要用到 redux-immutable 中的方法。

# 创建 store

在 store 文件夹下面新建 index.js 和 reducer.js 文件:

//reducer.js
import { combineReducers } from 'redux-immutable';

export default combineReducers ({
// 之后开发具体功能模块的时候添加 reducer
});
//index.js
import { createStore, compose, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore (reducer, composeEnhancers (
  applyMiddleware (thunk)
));

export default store;

# 项目中注入 store

现在 App.js 中代码如下:

import React from 'react'
import { Provider } from 'react-redux'
import { GlobalStyle } from  './style'
import { renderRoutes } from 'react-router-config'
import { IconStyle } from './assets/iconfont/iconfont'
import store from './store/index'
import routes from './routes/index.js'
import { HashRouter } from 'react-router-dom';

function App () {
  return (
    <Provider store={store}>
      <HashRouter>
        <GlobalStyle></GlobalStyle>
        <IconStyle></IconStyle>
        { renderRoutes (routes) }
      </HashRouter>
    </Provider>
  )
}

export default App;

现在功能依旧能用,但是打开控制台会有这样一段报错:

img

因为现在没有开发出具体的 reducer 函数,没关系,随着之后的开发,这个错误会自动消失。

阅读全文