react 项目实战(一)搭建项目

1,028 阅读5分钟

脚手架

  • 脚手架搭建项目,清理不需要的文件
  • sudo npm install -g create-react-app (MAC下需加上sudo)
  • create-react-app 项目名称 创建项目,清理不需要的文件

craco

因为我们要改配置项才可以更改别名,但是呢?react的脚手架呢并不希望我们暴露别名 我们可以通过这个进行覆盖

配置@ 作为src 的别名 也可以呢配置其他的别名

  • yarn add @craco/craco 新建:craco.config.js
const path = require("path");

const resolve = dir => path.resolve(__dirname, dir);

module.exports = {
  webpack: {
    alias: {
      "@": resolve("src"),
      "components": resolve("src/components")
    }
  }
}

index.js

从文件入口书写代码

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import "@/assets/css/reset.css"
ReactDOM.render(<App />, document.getElementById('root'))
  • 引入React
  • react-dom:react渲染在不同平台所需要的核心代码;
  • react-dom针对web和native所完成的事情不同:
    • web端:react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。
  • 引入入口组件
  • 重置的css样式文件
  • 渲染组件

reset.css

我们先来看重置的css样式吧

@import "~normalize.css";
@import "~antd/dist/antd.css";

/* 样式的重置 */
body, html, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, header, menu, section, p, input, td, th, ins {
  padding: 0;
  margin: 0;
}

ul, ol, li {
  list-style: none;
}

a {
  text-decoration: none;
  color: #666;
}

a:hover {
  color: #666;
  text-decoration: underline;
}

i, em {
  font-style: normal;
}

input, textarea, button, select, a {
  outline: none;
  border: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

img {
  border: none;
  vertical-align: middle;
}

/* 全局样式 */
body, textarea, select, input, button {
  font-size: 12px;
  color: #333;
  font-family: Arial, Helvetica, sans-serif;
  background-color: #f5f5f5;
}

.text-nowrap {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.wrap-v1 {
  width: 1100px;
  margin: 0 auto;
}

.wrap-v2 {
  width: 980px;
  margin: 0 auto;
}
  • normalize 这个是我们最常用的重置样式的css文件,我们可以直接下载,也可以 yarn add normalize.css
  • antd/dist/antd.css 这个是antd 的样式
  • ~ 这个波浪线表示的是导入某个模块的东西
  • 在这个文件中我们还可以写自己的 /* 样式的重置 / 还有就是全局的样式 / 全局样式 */

App.js

入口组件

// 第三方
import React, { memo, Suspense } from 'react';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
// 自己配置
import routes from './router';
import store from './store';
// 组件
import { HashRouter } from 'react-router-dom';
import HYAppHeader from '@/components/app-header';
import HYAppFooter from '@/components/app-footer';
import HYAppPlayerBar from './pages/player/app-player-bar';

export default memo(function App() {
  return (
    <Provider store={store}>
      <HashRouter>
        <HYAppHeader />
        <Suspense fallback={<div>page loading</div>}>
          {renderRoutes(routes)}
        </Suspense>
        <HYAppFooter />
        <HYAppPlayerBar />
      </HashRouter>
    </Provider>
  )
})

  • 从React结构出来的memo,Suspense是啥来着
    • React.memo是一个高阶组件,类似于React.PureComponent,不同于React.memo是function组件,React.PureComponent是class组件。
    • Suspense使用的时候,fallback一定是存在且有内容的,否则会报错
    • lazy and suspence 动态导入主要应用场景是延迟加载方法
  • Provider 主要用来传递数据的吧和context有点类似
  • 使用renderRoutes方法继续渲染子路由,第二个参数可以进行传参
  • BrowserRouter或HashRouter
    • Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;
    • BrowserRouter使用history模式;
    • HashRouter使用hash模式;

router

开始路由的配置

router/index.js

import React from 'react'
import { Redirect } from "react-router-dom"

const HYDiscover = React.lazy(()=>import("@/pages/discover"));
const HYRecommend =  React.lazy(_=>import("../pages/discover/c-pages/recommend"))
const HYFriend = React.lazy(_=>import("../pages/friend"))

// import HYDiscover from "@/pages/discover"
// import HYRecommend from '../pages/discover/c-pages/recommend'
// import HYFriend from '@/pages/friend'

const routes = [
  {
    path:'/',
    exact:true,
    render:()=>(
      <Redirect to="/discover"/>
    )
  },
  {
    path:"/discover",
    components:HYDiscover,
    routes:[
      {
        path:"/discover",
        exact:true,
        render:()=>(
          <Redirect to="/discover/recommend" />
        )
      },
      {
        path:"/dicover/recommend",
        component:HYRecommend
      }
    ]
  },
  {
    path:"/friend",
    component:HYFriend
  }
]

export default routes
  • Redirect 路由提供的重定向
  • 引入组件的懒加载方式 但是这个有点不太明白 _ => 这个下划线是个什么鬼
  • exact 精确的
  • 路由定向的方式 render:()=>(<Redirect to="/discover")
  • 将路由规则暴露出去

这里就是简单的路由配置信息了,那么如果我们需要像vue-router 配置一些信息呢? meta

  import { renderRoutes } from 'react-router-config' 
  
          <Suspense>
          { renderRoutes(routes)}
        </Suspense>

header

当路由配置好了,那么整个项目基础部分应该算是完工了。然后回到App.js入口的组件开始进行布局 首先是头部的部分

在这个项目中,我们用的应该都是函数是组件,因为结合Hooks去使用

import React, { memo } from 'react'
import { headerLinks } from '@/comon/local-data'

import { NavLink } from 'react-router-dom'
import { SearchOutlined } from '@ant-design/icons'
import { Input } from 'antd'
import {
  HeaderWrapper,
  HeaderLeft,
  HeaderRight
} from './style'

export default memo(function HYAppHeader() {
  const showSelectItem = (item, index) => {
    if (index < 3) {
      return (
        <NavLink to={item.link}>
          {item.title}
          <i className="sprite_01 icon"></i>
        </NavLink>
      )
    }else {
    return <a href={item.link}>{item.title}</a>
    }
  }
// 返回的jsx
  return (
    <HeaderWrapper>
      <div className="content wrap-v1">
        <HeaderLeft>
          <a href="#/" className="logo sprite_01">网易云音乐</a>
          <div className="select-list">
            {
              headerLinks.map((item,index)=>{
                return (
                  <div key={item.title} className="select-item">
                    {
                      showSelectItem(item,index)
                    }
                  </div>
                )
              })
            }
          </div>
        </HeaderLeft>
        <HeaderRight>
          <Input className="search" placeholder="音乐/视频/电台/用户" prefix={<SearchOutlined />} />
          <div className="center">创作者中心</div>
          <div>登录</div>
        </HeaderRight>
      </div>
      <div className="divider"></div>
    </HeaderWrapper>
  )
})

  • memo 记得是包裹函数式组件的
  • headerLinks 是内部定义的数据
  • NavLink是在Link基础之上增加了一些样式属性(后续学习);to属性:Link中最重要的属性,用于设置跳转到的路径;

styled-components

styled-components的本质是通过函数的调 用,最终创建出一个组件:

  • 这个组件会被自动添加上一个不重复的 class;
  • styled-components会给该class添加相 关的样式; 还有更多高阶的样式后面再说吧
import styled from "styled-components";

export const HeaderWrapper = styled.div`
  height: 75px;
  font-size: 14px;
  color: #fff;
  background-color: #242424;

  .content {
    height: 70px;

    display: flex;
    justify-content: space-between;
  }

  .divider {
    height: 5px;
    background-color: #C20C0C;
  }
`

footer

import React, { memo, Fragment } from 'react';

import { footerLinks, footerImages } from "@/common/local-data";

import {
  AppFooterWrapper,
  FooterLeft,
  FooterRight,
} from './style';

export default memo(function HYAppFooter() {
  return (
    <AppFooterWrapper>
      <div className="wrap-v2 content">
        <FooterLeft className="left">
          <div className="link">
            {
              footerLinks.map(item => {
                return (
                  <Fragment key={item.title}>
                    <a href={item.link} target="_blank" rel="noopener noreferrer">{item.title}</a>
                    <span className="line">|</span>
                  </Fragment>
                )
              })
            }
          </div>
          <div className="copyright">
            <span>网易公司版权所有©1997-2020</span>
            <span>
              杭州乐读科技有限公司运营:
            <a href="https://p1.music.126.net/Mos9LTpl6kYt6YTutA6gjg==/109951164248627501.png" rel="noopener noreferrer" target="_blank">浙网文[2018]3506-263号</a>
            </span>
          </div>
          <div className="report">
            <span>违法和不良信息举报电话:0571-89853516</span>
            <span>
              举报邮箱:
            <a href="mailto:ncm5990@163.com" target="_blank" rel="noopener noreferrer">ncm5990@163.com</a>
            </span>
          </div>
          <div className="info">
            <span>粤B2-20090191-18</span>
            <a href="http://www.beian.miit.gov.cn/publish/query/indexFirst.action" rel="noopener noreferrer" target="_blank">
              工业和信息化部备案管理系统网站
          </a>
          </div>
        </FooterLeft>
        <FooterRight className="right">
          {
            footerImages.map((item, index) => {
              return (
                <li className="item" key={item.link}>
                  <a className="link" href={item.link} rel="noopener noreferrer" target="_blank"> </a>
                  <span className="title">{item.title}</span>
                </li>
              )
            })
          }
        </FooterRight>
      </div>
    </AppFooterWrapper>
  )
})

body

import React, { memo } from 'react';
import { renderRoutes } from 'react-router-config';

import { dicoverMenu } from "@/common/local-data";

import { NavLink } from 'react-router-dom';
import {
  DiscoverWrapper,
  TopMenu
} from './style';

export default memo(function HYDiscover(props) {
  const { route } = props;
  console.log('props',props)

  return (
    <DiscoverWrapper>
      <div className="top">
        <TopMenu className="wrap-v1">
          {
            dicoverMenu.map((item, index) => {
              return (
                <div className="item" key={item.title}>
                  <NavLink to={item.link}>{item.title}</NavLink>
                </div>
              )
            })
          }
        </TopMenu>
      </div>
      {renderRoutes(route.routes)}
    </DiscoverWrapper>
  )
})
  • console.log('props',props) 路由渲染的组件都会有props 获取到的路由信息吗
  • {renderRoutes(route.routes)} 渲染对应的子组件

top-banner

import React, { memo, useEffect, useRef, useCallback, useState } from 'react';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';

import { getTopBannerAction } from '../../store/actionCreators';

import { Carousel } from 'antd';
import {
  BannerWrapper,
  BannerLeft,
  BannerRight,
  BannerControl
} from './style';

export default memo(function HYTopBanner() {
  // state
  const [currentIndex, setCurrentIndex] = useState(0);

  // 组件和redux关联: 获取数据和进行操作
  const { topBanners } = useSelector(state => ({
    // topBanners: state.get("recommend").get("topBanners")
    topBanners: state.getIn(["recommend", "topBanners"])
  }), shallowEqual);
  const dispatch = useDispatch();

  // 其他hooks
  const bannerRef = useRef();
  useEffect(() => {
    dispatch(getTopBannerAction());
  }, [dispatch]);
  const bannerChange = useCallback((from, to) => {
    setCurrentIndex(to);
  }, []);

  // 其他业务逻辑
  const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")

  return (
    <BannerWrapper bgImage={bgImage}>
      <div className="banner wrap-v2">
        <BannerLeft>
          <Carousel effect="fade" autoplay ref={bannerRef} beforeChange={bannerChange}>
            {
              topBanners.map((item, index) => {
                return (
                  <div className="banner-item" key={item.imageUrl}>
                    <img className="image" src={item.imageUrl} alt={item.typeTitle} />
                  </div>
                )
              })
            }
          </Carousel>
        </BannerLeft>
        <BannerRight></BannerRight>
        <BannerControl>
          <button className="btn left" onClick={e => bannerRef.current.prev()}></button>
          <button className="btn right" onClick={e => bannerRef.current.next()}></button>
        </BannerControl>
      </div>
    </BannerWrapper>
  )
})

useState

  • useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为 undefined)。
  • useState是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。