React 去哪旅行实战项目--学习笔记(1)(持续更新)

去哪旅行实战项目

一、 初始化项目

  1. 使用脚手架创建默认模板文件

    npx create-react-app train-tickers
    
  2. 使用eject 命令更改 webpack 配置, 将封装在 create-react-app中的配置全部反编译到当前项目,这样用户就能完全取得 webpack 文件的控制权

    npm run ejext
    

    报错:

    create-react-app 执行 yarn eject以后yarn start报错

    步骤:

    ​ 其实就是重写安装依赖

    1. rm -R node_modules/
    2. rm yarn.lock
    3. yarn
  3. 删除多余文件

    进入 ./src 目录, 执行遍历并删除除了 serviceWorker.js文件以外的所有文件

    ls | grep -v serviceWorker.js | xargs rm
    
  4. 进入./src 目录,创建初始化文件

    ./src/index/

    cd index/
    touch index.js
    touch index.css
    touch App.jsx
    touch App.css
    touch reducers.js
    touch actions.js
    touch store.js
    
  5. 创建初始文件的原始代码

    ./src/index/index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import {Provider} from 'react-redux'
    
    import store from './store'
    import './index.css';
    import App from './App.jsx'
    
    ReactDOM.render(
        <Provider store={store}><App /></Provider>,
        document.getElementById('root')
    )
    

    建议再安装并导入normalize.css/normailze.css这个css用于自动适配项目在各个浏览器上的样式,不用自己再手动适配。

    安装,也是使用npm方式

    切换到根目录
    src/index   cd ..
    src/  cd ..
    ./  npm i normalize.css
    

    报错:

    normalize.css/normailze.css找不到这个包

    解决方式:

    复制normailze.cssutils文件,导入即可。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlwqfKtm-1584099786657)(C:\Users\80450\AppData\Roaming\Typora\typora-user-images\image-20200312113623963.png)]

    import '../utils/normalize.css'
    

    ./src/index/App.js

    import { connect } from 'react-redux';
    import './App.css';
    
    function App(props) {
    
    }
    
    export default connect(
        function mapstateToProps(state){
    
        },
        function mapDispaTchtoProps(dispatch){
    
        }
    )(App);
    

    ./src/index/reducers.js

    export default {};
    
  6. 创建 搜索结果query坐席选择ticket订单填写order,目录结构和index相同,所以直接将index目录拷贝壳

    ./src/index/

    ./src/index/   cd src
    ./src/         cp -r index query
    ./src/         cp -r index ticket
    ./src/         cp -r index order
    
  7. public目录中删除多余文件,创建query.html, ticket.html, order.html

    ./src/       cd ../public
    
    ls
    
    Mode                LastWriteTime         Length Name
    ----                -------------         ------ ----
    -a----         2020/3/1     17:44           3150 favicon.ico
    -a----         2020/3/1     17:44           1721 index.html
    -a----         2020/3/1     17:44           5347 logo192.png
    -a----         2020/3/1     17:44           9664 logo512.png
    -a----         2020/3/1     17:44            492 manifest.json
    -a----         2020/3/1     17:44             67 robots.txt
    
    rm .\logo192.png
    rm .\logo512.png
    
    cp index.html query.html
    cp index.html ticket.html
    cp index.html order.html
    

    然后修改复制生成的HTML的title

    ./public/order.html

    <title>React App</title> 改为  <title>订单填写</title>
    

    ./public/query.html

    <title>React App</title> 改为  <title>搜索结果</title>
    

    ./public/titcket.html

    <title>React App</title> 改为  <title>坐席选择</title>
    
  8. 当前webpacketry不满足需求,需手动修改etry

    ./config/webpack.config.js

    module.exports = function(webpackEnv) {
      const isEnvDevelopment = webpackEnv === 'development';
      const isEnvProduction = webpackEnv === 'production';
    	......
        
    	entry: [
          // Include an alternative client for WebpackDevServer. A client's job is to
          // connect to WebpackDevServer by a socket and get notified about changes.
          // When you save a file, the client will either apply hot updates (in case
          // of CSS changes), or refresh the page (in case of JS changes). When you
          // make a syntax error, this client will display a syntax error overlay.
          // Note: instead of the default WebpackDevServer client, we use a custom one
          // to bring better experience for Create React App users. You can replace
          // the line below with these two lines if you prefer the stock client:
          // require.resolve('webpack-dev-server/client') + '?/',
          // require.resolve('webpack/hot/dev-server'),
          isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient'),
          // Finally, this is your app's code:
          paths.appIndexJs,
          // We include the app code last so that if there is a runtime error during
          // initialization, it doesn't blow up the WebpackDevServer client, and
          // changing JS code would still trigger a refresh.
        ].filter(Boolean),
        ......
    

    修改为:

    // 数组型entry不满足要求,因此改为对象
    entry:{
        index:[paths.appIndexJs,isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient')].filter(Boolean),
        query:[paths.appQueryJs,isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient')].filter(Boolean),
        ticket:[paths.appTicketJs,isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient')].filter(Boolean),
        order:[paths.appOrderJs,isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient')].filter(Boolean),
    }
    

    ./config/paths.js

    // config after eject: we're in ./config/
    module.exports = {
      dotenv: resolveApp('.env'),
      appPath: resolveApp('.'),
      appBuild: resolveApp('build'),
      appPublic: resolveApp('public'),
      // 扩充html
      appHtml: resolveApp('public/index.html'),
      appQueryHtml: resolveApp('public/query.html'),  
      appTicketHtml: resolveApp('public/ticket.html'),  
      appOrderHtml: resolveApp('public/order.html'),  
      // 修改index的路径
      // appIndexJs: resolveModule(resolveApp, 'src/index'),
      appIndexJs: resolveModule(resolveApp, 'src/index/index'),
      appQueryJs: resolveModule(resolveApp, 'src/query/index'),
      appTicketJs: resolveModule(resolveApp, 'src/ticket/index'),
      appOrderJs: resolveModule(resolveApp, 'src/order/index'),
      appPackageJson: resolveApp('package.json'),
      appSrc: resolveApp('src'),
      appTsConfig: resolveApp('tsconfig.json'),
      appJsConfig: resolveApp('jsconfig.json'),
      yarnLockFile: resolveApp('yarn.lock'),
      testsSetup: resolveModule(resolveApp, 'src/setupTests'),
      proxySetup: resolveApp('src/setupProxy.js'),
      appNodeModules: resolveApp('node_modules'),
      publicUrlOrPath,
    };
    

    报错:

    Cannot read property ‘filter’ of undefined

    ManifestPlugin这个插件的作用是生成一份.json的文件,通过该文件的映射关系可以让我们知道webpack是如何追踪所有模块并映射到输出bundle中的。我们先来看原始的配置,这里fileName设置了输出文件名asset-manifest.jsonpublicPath设置了输出路径,最终要的是最后一个generate参数,自定义了输出的内容,里面有一段是取entrypoints.main,这是针对单一入口的配置,因为单一入口不指定name的情况默认namemain,当改成多入口的方式了之后这里面在entrypoints中自然是读取不到main这个值的,因此就报错,这里将generate这个参数去掉,恢复其默认值即可,或者将entrypoints这个key去掉。

     new ManifestPlugin({
            fileName: 'asset-manifest.json',
            publicPath: paths.publicUrlOrPath,
            // generate: (seed, files, entrypoints) => {
            //   const manifestFiles = files.reduce((manifest, file) => {
            //     manifest[file.name] = file.path;
            //     return manifest;
            //   }, seed);
            //   const entrypointFiles = entrypoints.main.filter(
            //     fileName => !fileName.endsWith('.map')
            //   );
    
            //   return {
            //     files: manifestFiles,
            //     entrypoints: entrypointFiles,
            //   };
            // },
          }),
    

    或者

      new ManifestPlugin({
            fileName: 'asset-manifest.json',
            publicPath: paths.publicUrlOrPath,
            generate: (seed, files, entrypoints) => {
              const manifestFiles = files.reduce((manifest, file) => {
                manifest[file.name] = file.path;
                return manifest;
              }, seed);
              // const entrypointFiles = entrypoints.main.filter(
              //   fileName => !fileName.endsWith('.map')
              // );
    
              return {
                files: manifestFiles,
                // entrypoints: entrypointFiles,
              };
            },
          }),
    
  9. 最后,编译一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ktCG93d-1584099786658)(C:\Users\80450\AppData\Roaming\Typora\typora-user-images\image-20200312113759148.png)]

成功运行。

二、搭建Mock server

三、数据结构和模块设计

首先设计项目的整体轮廓。

  1. index目录下,创建行程组件Journey、高铁班次组件HighSpeed、行程日期组件DepartDate、提交按钮Submit

    ./src/index/Journey

    import React from 'react'
    import './DepartDate.css';
    
    /*
      行程日期组件
    */
    export default function DepartDate(props) {
        return (
            <div>
    
            </div>
        );
    }
    
    

    ./src/index/HighSpeed

    import React from 'react'
    import './HighSpeed.css';
    
    /*
      高铁班次
    */
    export default function HighSpeed(props) {
        return (
            <div>
    
            </div>
        );
    }
    

    ./src/index/DepartDate

    import React from 'react'
    import './DepartDate.css';
    
    /*
      行程日期组件
    */
    export default function DepartDate(props) {
        return (
            <div>
    
            </div>
        );
    }
    
    

    ./src/index/Submit

    import React from 'react'
    import './Submit.css';
    
    /*
      提交按钮
    */
    export default function Submit(props) {
        return (
            <div>
    
            </div>
        );
    }
    
  2. 设计store里参数的默认值

    ./src/index/store.js

    import {
        createStore,
        combineReducers,
        applyMiddleware
    } from 'redux'
    
    import reducers from './reducers';
    import thunk from 'redux-thunk';
    
    export default createStore(
        combineReducers(reducers),
        {
            from: '北京', // 出发站
            to: '上海', // 终止站
            isCitySelectorVisible: false, // 复程,默认false
            currentSelectingLeftCity: false,// 选择复程,数据回填即 from和to更换,默认false
            cityData: null,//选择行程后城市的数据加载,默认值为空
            isLoadingCityData: false, //用于节流操作,判断当前是否在发起城市数据加载,如果是true就是需要发起,false就是不需要。默认值为false
            isDateSelectorVisible: false,//选择行程后日期选择的开关,默认值为false
            highSpeed: false//是否选择了高铁,默认值为false
        },
        applyMiddleware(thunk)
    )
    
  3. 设计actionTypes:

    ./src/index/actions.js

    export const ACTION_SET_FROM = 'FROM'; // 出发站
    export const ACTION_SET_TO = 'TO'; // 终止站
    export const ACTION_SET_IS_CITY_SELECTOR_VISIBLE = 'IS_CITY_SELECTOR_VISIBLE'; //是否选择复程 
    export const ACTION_SET_CURRENT_SELECTING_LEFT_CITY = 'CURRENT_SELECTING_LEFT_CITY';// 选择复程后城市回填
    export const ACTION_SET_CITY_DATA = 'CITY_DATA'; // 选择城市的数据
    export const ACTION_SET_IS_LOADING_CITY_DATA = 'IS_LOADING_CITY_DATA'; // 发送城市数据请求
    export const ACTION_SET_IS_DATE_SELECTOR_VISIBLE = 'IS_DATE_SELECTOR_VISIBLE'; // 选择复程后日期选择的开关
    export const ACTION_SET_HIGH_SPEED = 'HIGH_SPEED'; // 高铁班次
    
    
  4. 设计action:

    ./src/index/actions.js

    // 出发站
    export function setFrom(from) {
        return {
            type: ACTION_SET_FROM,
            payload: from, 
        }
    }
    
    // 终止站
    export function setTo(to){
        return {
            type: ACTION_SET_TO,
            payload: to,
        }
    }
    
    // 发起城市数据请求
    export function setIsLoaingCityData(isLoadingCityData) {
        return {
            type: ACTION_SET_IS_LOADING_CITY_DATA,
            payload: isLoadingCityData,
        }
    }
    
    // 加载城市数据
    export function setCityData(cityData){
        return{
            type: ACTION_SET_CITY_DATA,
            payload: cityData,
        }
    }
    
    // 切换高铁
    export function toggleHighSpeed(){
        // 异步发送
        return (dispatch, getState) => {
            const { highSpeed } = getState();
            dispatch({
                type: ACTION_SET_HIGH_SPEED,
                payload: !highSpeed,
            })
        }
    }
    
    // 显示,选择行程
    export function showCitySelector(currentSelectingLeftCity){
        return (dispatch) => {
            dispatch({
                type: ACTION_SET_IS_CITY_SELECTOR_VISIBLE,
                payload: true,
            });
    
            dispatch({
                type: ACTION_SET_CURRENT_SELECTING_LEFT_CITY,
                payload: currentSelectingLeftCity,
            });
        }
    }
    
    // 隐藏,选择行程
    export function hideCitySelector(){
        return {
            type: ACTION_SET_IS_CITY_SELECTOR_VISIBLE,
            payload: false
        }
    }
    
    // 回填城市, 终止站有则回填
    export function setSelectedCity(city) {
        return (dispatch, getState) => {
            const { currentSelectingLeftCity } = getState();
            if (currentSelectingLeftCity) {
                dispatch(setFrom(city));
            } else {
                dispatch(setTo(city));
            }
        }
    }
    
    
    // 显示选择日期
    export function showDateSelector() {
        return {
            type: ACTION_SET_IS_DATE_SELECTOR_VISIBLE,
            payload: true,
        }
    }
    
    // 隐藏选择日期
    export function hideDateSelector(){
        return {
            type: ACTION_SET_IS_DATE_SELECTOR_VISIBLE,
            payload: false,
        }
    }
    
    // 出发站 和 终止站 互换
    export function exchangeFromTo() {
        return (dispatch,getState) =>{
            const { from, to } = getState();
            dispatch(setFrom(to));
            dispatch(setTo(from));
        }
    }
    
  5. 设计reducers更新store状态:

    ./src/index/reducers.js

    import {
        ACTION_SET_FROM,
        ACTION_SET_TO,
        ACTION_SET_IS_CITY_SELECTOR_VISIBLE,
        ACTION_SET_CURRENT_SELECTING_LEFT_CITY,
        ACTION_SET_CITY_DATA,
        ACTION_SET_IS_LOADING_CITY_DATA,
        ACTION_SET_IS_DATE_SELECTOR_VISIBLE,
        ACTION_SET_HIGH_SPEED,
    } from './actions.js'
    
    /**写法:
    *function moudle (state={初始状态}, action){
    *	switch(action.type){
    *		case ACTION_TYPE:
    *			return payload;
    *		default:
    *			return state;
    *	}
    *}
    */ 
    
    export default {
        from(state="北京",action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_FROM :
                    return payload;
                default:
                    return state;
            }
        },
        to(state="上海",action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_TO:
                    return payload;
                default:
                    return state;
            }
        },
        isCitySelectorVisible(state=false,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_IS_CITY_SELECTOR_VISIBLE:
                    return payload;
                default:
                    return state;
            }
        },
        currentSelectingLeftCity(state=false,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_CURRENT_SELECTING_LEFT_CITY:
                    return payload;
                default:
                    return state;
            }
        },
        cityData(state=null,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_CITY_DATA:
                    return payload;
                default:
                    return state;
            }
        },
        isLoadingCityData(state=false,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_IS_LOADING_CITY_DATA:
                    return payload;
                default:
                    return state;
            }
        },
        isDateSelectorVisible(state=false,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_IS_DATE_SELECTOR_VISIBLE:
                    return payload;
                default:
                    return state;
            }
        },
        highSpeed(state=false,action) {
            const {type,payload} = action;
            switch(type){
                case ACTION_SET_HIGH_SPEED:
                    return payload;
                default:
                    return state;
            }
        },
    }
    

四、顶部导航栏

  1. 在公共组件目录中,创建Header.jsx

    ./src/common/

    import React from 'react'
    import PropTypes from 'prop-types';
    import './Header.css';
    
    
    export default function Header(props) {
        const { onBack, title } = props;
        return (
            <div className="header">
                <div className="header-back" onClick={onBack}>
                    <svg width="42" height="42">
                        <polyline 
                            points="25,13 16,21 25,29"
                            stroke="#fff"
                            strokeWidth="2"
                            fill="none"
                        />
                    </svg>
                </div>
                <div>
                    <h1 className="header-title">
                        { title }
                    </h1>
                </div>
            </div>
        );
    }
    
    Header.propTypes={
        onBack : PropTypes.func.isRequired,
        title : PropTypes.string.isRequired
    }
    
  2. ./src/index/App.jsx

    因为直接挂载组件时,组件没有完全置于顶部,所以增加一个

    标签包裹,通过div标签自定义位置

    import React,{Fragment,useCallback} from 'react';
    import { connect } from 'react-redux';
    import './App.css';
    
    import Header from '../common/Header.jsx';
    import DepartDate from './DepartDate.jsx';
    import HighSpeed from './HighSpeed.jsx';
    import Journey from './Journey.jsx';
    import Sumbit from './Submit.jsx';
    
    function App(props) {
        const onBack = useCallback(() => {
            window.history.back();
            console.log('我是你爸');
        },[]);
        return (
            <Fragment>
                <div className="header-wrapper">
                    <Header title="火车票" onBack={onBack}/>
                </div>
                
                <Journey />
                <DepartDate />
                <HighSpeed />
                <Sumbit />
            </Fragment>
        )
    };
    
    export default connect(
        function mapStateToProps(state){
            return {};
        },
        function mapDispatchToProps(dispatch){
            return {};
        }
    )(App);
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值