脚手架
- 脚手架搭建项目,清理不需要的文件
- 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是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。