博客管理系统开发 -- 基于React前端框架搭建

余生颓废 提交于 2020-04-05 21:24:08

一、前端项目结构

在上一节的基础上,我们分别在src下创建如下文件夹:

  • assets:静态文件;
  • components:公共组件,比如面包屑、编辑器、svg图标、分页器等等;
  • hooks:函数组件,使用 React 16.8引进的Hook 特性实现;
  • layout:布局组件;
  • redux:redux目录,负责状态管理;
  • routes:路由,负责路由管理;
  • styles:全局样式;
  • utils:工具包;
  • views:视图层;

二、redux目录构建

我们项目使用redux进行状态管理,在使用redux状态管理器之前,我们需要安装依赖包:

npm install redux --save 
npm install react-redux --save
npm install redux-logger --save
npm install redux-thunk --save
npm install redux-devtools-extension --save

1、在redux文件夹下创建root_reducers.js文件,用于保存整个项目使用到的reducer:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: 合并reducer
 */
import {combineReducers} from 'redux';

export default combineReducers({})

这里利用 combineReducers 函数来把多个 reducer 函数合并成一个 reducer 函数,目前还没有引入redux函数,后面我们会逐渐完善。

2、在redux文件夹下创建Index.js文件:

/**
 * @author zy
 * @date 2020/4/4
 * @Description: redux状态管理器配置
 * 不懂原理的可以参考:https://github.com/brickspert/blog/issues/22#middleware
 */
import thunk from 'redux-thunk';
import {compose, createStore, applyMiddleware} from 'redux';
import rootReducers from './root_reducers';
import {composeWithDevTools} from 'redux-devtools-extension';

const storeEnhancers = process.env.NODE_ENV === 'production' ? compose(applyMiddleware(thunk)) :
    compose()(composeWithDevTools(applyMiddleware(thunk)));

/**
 * 创建store
 * @author zy
 * @date 2020/4/5
 */
const configureStore = () => {
    //创建store对象
    const store = createStore(rootReducers, storeEnhancers);

    //reducer热加载
    if (process.env.NODE_ENV !== 'production') {
        if (module.hot) {
            module.hot.accept('./root_reducers', () => {
                store.replaceReducer(rootReducers)
            })
        }
    }

    return store;
}

export default configureStore();

这里我们利用createStore创建了一个状态管理器,并传入了redux,此外我们还使用了thunk中间件来处理异步请求。

如果不理解这部分代码,可以先去看一下redux相关知识:

[1]完全理解 redux(从零实现一个 redux)

[2]浅谈对于react-thunk中间件的简单理解

三、routes目录构建

路由构建是使用React Route路由库实现的,在使用之前,我们需要安装以下依赖:

npm install react-router-dom --save

1、在routes文件夹下创建web.js文件:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: web路由
 * 不懂的可以参考:https://segmentfault.com/a/1190000020812860
 * https://reacttraining.com/react-router/web/api/Route
 */
import React from 'react';
import PageNotFound from '@/components/404';

function Home(props) {
    console.log('Home=>', props);
    return <h2>Home</h2>
}

function About(props) {
    console.log('About=>', props);
    return <h2>About</h2>;
}

/**
 * web路由配置项
 * @author zy
 * @date 2020/4/5
 */
export default {
    path: '/',
    name: 'home',
    component: Home,
    childRoutes: [
        {path: 'about', component: About},
        {path: '*', component: PageNotFound}
    ]
}

可以看到我们最后导出了web路由配置项,访问/会加载Home组件,访问/about会加载About组件,访问其它页面会返回页面找不到组件。

2、在routes下创建index.js文件:

import React from 'react';
import {Switch, Route} from 'react-router-dom';
import _ from 'lodash';
import webRouteConfig from './web';

//保存所有路由配置的数组
const routeConfig = [webRouteConfig]

/**
 * 路由配置
 * @author zy
 * @date 2020/4/5
 */
export default function () {

    /**
     * 生成路由嵌套结构
     * @author: zy
     * @date: 2020-03-05
     * @param routeConfig: 路由配置数组
     * @param contextPath: 路由根路径
     */
    const renderRouters = (routeConfig, contextPath = '/') => {
        const routes = [];

        const renderRoute = (item, routeContextPath) => {

            //基路径
            let path = item.path ? `${contextPath}/${item.path}` : contextPath
            path = path.replace(/\/+/g, '/')

            if (!item.component) {
                return;
            }

            //当前路由
            routes.push(
                <Route
                    key={path}
                    path={path}
                    component={item.component}
                    exact
                />
            );

            //子路由
            if (item.childRoutes) {
                _.forEach(item.childRoutes, item => {
                    renderRoute(item, path);
                })
            }
        };

        _.forEach(routeConfig, item => renderRoute(item, contextPath))

        return <Switch>{routes}</Switch>
    };

    return renderRouters(routeConfig);
}

如果不理解这部分代码,可以先去看一下react router相关知识:

[1]react-router-dom@5.x官方文档翻译

[2]react-router官方手册

四、components目录构建

在web.js中我们使用到了PageNotFound组件,我们需要在components下创建404文件,并在该文件夹下创建index.jsx文件,代码如下:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: 找不到页面
 */
import React from 'react';
import {Result, Button} from 'antd';

/**
 * 页面找不到组件
 * @author zy
 * @date 2020/4/5
 */
function PageNotFound(props) {
    return (
        <Result
            status='404'
            title='404'
            subTitle='Sorry, the page you visited does not exist.'
            extra={
                <Button
                    type='primary'
                    onClick={() => {
                        props.history.push('/')
                    }}>
                    Back Home
                </Button>
            }
        />
    )
}

export default PageNotFound

由于此处我们使用了antd组件,因此需要引入依赖:

cnpm install antd --save

关于更多antd组件的使用请查看:antd官网

五、hooks目录构建

我们在hooks文件夹下创建use_bus.js文件,该文件用于构建事件监听器,后面我们用到会具体介绍:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: 事件监听器
 * useContext Hook 是如何工作的:https://segmentfault.com/a/1190000020111320?utm_source=tag-newest
 * useEffect Hook 是如何工作的:https://segmentfault.com/a/1190000020104281
 * 微型库解读之200byte的EventEmitter - Mitt:https://segmentfault.com/a/1190000012997458?utm_source=tag-newest
 */
import React from 'react';
import mitt from 'mitt';

//创建上下文
const context = React.createContext();

//外层提供数据的组件
const Provider = context.Provider;

//useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
export function useBus() {
    return React.useContext(context);
}

/**
 * 事件监听器组件
 * @author zy
 * @date 2020/4/5
 * @param name:监听的事件名称
 * @param fn:事件发布时的响应函数
 */
export function BusListener(name, fn) {
    //获取 context 的当前值
    const bus = useBus();

    //组件第一次挂载执行,第二个参数发生变化时执行
    React.useEffect(() => {
        //事件定阅
        bus.on(name, fn);
        //组件卸载之前执行
        return () => {
            //取消事件定阅
            bus.off(name, fn);
        }
    }, [bus, name, fn])
}

//外层提供数据的组件 向后代组件跨层级传值bus,这样后代组件都可以通过useBus获取到bus的值
export function BusProvider({children}) {
    const [bus] = React.useState(() => mitt());
    return <Provider value={bus}>{children}</Provider>
}

这里使用到了React 16.8引进的Hook新特性,感兴趣可以查看以下博客:

[1]useContext Hook 是如何工作的

[2]useEffect Hook 是如何工作的

[3]微型库解读之200byte的EventEmitter - Mitt

六、App.js文件

我们修改App.js文件代码如下:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: 根组件
 */
import React from 'react';
import Routes from '@/routes';
import {BrowserRouter} from 'react-router-dom';

export default function App(props) {
    return (
        <BrowserRouter>
            <Routes/>
        </BrowserRouter>
    )
}

七、index.js文件

我们修改index.js文件如下:

/**
 * @author zy
 * @date 2020/4/5
 * @Description: 入口文件
 */
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {AppContainer} from 'react-hot-loader';
import {BusProvider} from '@/hooks/use_bus';
import {Provider} from 'react-redux';
import store from '@/redux';

ReactDOM.render(
    <AppContainer>
        <BusProvider>
            <Provider store={store}>
                <App/>
            </Provider>
        </BusProvider>
    </AppContainer>,
    document.getElementById('root')
)

这里我们引入了局部热更新,这样当我们修改部门文件时,不会造成整个页面的刷新,可以保留状态值。

npm install react-hot-loader --save

此外,我们还引入了状态管理器store,用来管理我们所有组件的状态。

在import文件的时候,我们引入了@别名,@指的的是src路径,其配置在webpack.config.js文件中:

至此,我们整个前端框架搭建完毕,我们可以运行程序,访问http://localhost:3000/

 此外,我们还可以访问about页面:

参考文章:

[1]完全理解 redux(从零实现一个 redux)

[2]浅谈对于react-thunk中间件的简单理解

[3]react-router-dom@5.x官方文档翻译

[4]react-router官方手册

[5]antd官方手册

[6]useContext Hook 是如何工作的

[7]useEffect Hook 是如何工作的

[8]微型库解读之200byte的EventEmitter - Mitt

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!