课程名称: React零基础入门到实战,完成企业级项目简书网站开发
课程章节: 课程总结
主讲老师: Dell
今天学习的内容包括:
如何进行项目的上线;
课程回顾;
htdocs是默认访问到根目录下的index.html文件
问题: 项目打包编译后 通过gh-pages部署的项打开之后是空白的
解答:捕获不到数据,json数据应该放在自己的服务器上
问题:登录状态刷新就没有了,数据如何持续化在 redux 中?
解答:利用localStorage做持久存储
问题:create-react-app 项目能打包原生APP应用吗
解答:不可以的,必须还要用cordova这样的东西给一个原生的壳
问题:将静态资源部署到服务器上后,点击浏览器的刷新,报404
解答:上线使用hashHistory,不要使用BrowserHistory
问题:项目上线之后,项目能跳转到详情页,但是刷新详情页,就会提示Not Found
解答:改用hashHistory的路由,否则需要后端改服务器配置
问题:部署到github pages上,public中的api文件怎么处理
解答:直接把接口数据写在代码里
问题:http-proxy-middleware-跨域-axios修改URL不起作用
解答:修改之后需要重启下服务器才生效
问题:如何固定Header在页面顶部
解答:HeaderWrapper添加fixed
问题:怎么使用script标签引入的qq定位js
解答:找到定位的js,放到script标签里
问题:react router 如何在回调事件里面控制跳转
解答:使用this.router.redirect
问题:react-router-dom怎么实现前进刷新后退不刷新
解答:可以用redux
react基础语法;redux数据层框架;react-redux; react-router4; immutable.js; style-components; reloadable.js
实例代码:
/package.json
{ "name": "jianshu", "version": "0.1.0", "private": true, "dependencies": { "axios": "^0.18.0", "immutable": "^3.8.2", "react": "^16.4.0", "react-dom": "^16.4.0", "react-loadable": "^5.4.0", "react-redux": "^5.0.7", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "react-scripts": "1.1.4", "react-transition-group": "^2.3.1", "redux": "^4.0.0", "redux-immutable": "^4.0.0", "redux-thunk": "^2.3.0", "styled-components": "^3.3.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } }
/src/App.js
import React, { Component } from 'react'; import { Provider } from 'react-redux'; import { BrowserRouter, Route } from 'react-router-dom'; import Header from './common/header'; import Home from './pages/home'; import Detail from './pages/detail/loadable.js'; import Login from './pages/login'; import Write from './pages/write'; import store from './store'; class App extends Component { render() { return ( <Provider store={store}> <BrowserRouter> <div> <Header /> <Route path='/' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route> <Route path='/write' exact component={Write}></Route> <Route path='/detail/:id' exact component={Detail}></Route> </div> </BrowserRouter> </Provider> ); } } export default App;
/src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './style.js'; import './statics/iconfont/iconfont'; ReactDOM.render(<App />, document.getElementById('root'));
/src/index.js
import { injectGlobal } from 'styled-components'; injectGlobal` html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } `;
/src/common/header/index.js
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { CSSTransition } from 'react-transition-group'; import { actionCreators } from './store'; import { actionCreators as loginActionCreators } from '../../pages/login/store' import { HeaderWrapper, Logo, Nav, NavItem, SearchWrapper, NavSearch, SearchInfo, SearchInfoTitle, SearchInfoSwitch, SearchInfoList, SearchInfoItem, Addition, Button } from './style'; class Header extends Component { getListArea() { const { focused, list, page, totalPage, mouseIn, handleMouseEnter, handleMouseLeave, handleChangePage } = this.props; const newList = list.toJS(); const pageList = []; if (newList.length) { for (let i = (page - 1) * 10; i < page * 10; i++) { pageList.push( <SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem> ) } } if (focused || mouseIn) { return ( <SearchInfo onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > <SearchInfoTitle> 热门搜索 <SearchInfoSwitch onClick={() => handleChangePage(page, totalPage, this.spinIcon)} > <i ref={(icon) => {this.spinIcon = icon}} className="iconfont spin"></i> 换一批 </SearchInfoSwitch> </SearchInfoTitle> <SearchInfoList> {pageList} </SearchInfoList> </SearchInfo> ) }else { return null; } } render() { const { focused, handleInputFocus, handleInputBlur, list, login, logout } = this.props; return ( <HeaderWrapper> <Link to='/'> <Logo/> </Link> <Nav> <NavItem className='left active'>首页</NavItem> <NavItem className='left'>下载App</NavItem> { login ? <NavItem onClick={logout} className='right'>退出</NavItem> : <Link to='/login'><NavItem className='right'>登陆</NavItem></Link> } <NavItem className='right'> <i className="iconfont"></i> </NavItem> <SearchWrapper> <CSSTransition in={focused} timeout={200} classNames="slide" > <NavSearch className={focused ? 'focused': ''} onFocus={() => handleInputFocus(list)} onBlur={handleInputBlur} ></NavSearch> </CSSTransition> <i className={focused ? 'focused iconfont zoom': 'iconfont zoom'}>  </i> {this.getListArea()} </SearchWrapper> </Nav> <Addition> <Link to='/write'> <Button className='writting'> <i className="iconfont"></i> 写文章 </Button> </Link> <Button className='reg'>注册</Button> </Addition> </HeaderWrapper> ); } } const mapStateToProps = (state) => { return { focused: state.getIn(['header', 'focused']), list: state.getIn(['header', 'list']), page: state.getIn(['header', 'page']), totalPage: state.getIn(['header', 'totalPage']), mouseIn: state.getIn(['header', 'mouseIn']), login: state.getIn(['login', 'login']) } } const mapDispathToProps = (dispatch) => { return { handleInputFocus(list) { (list.size === 0) && dispatch(actionCreators.getList()); dispatch(actionCreators.searchFocus()); }, handleInputBlur() { dispatch(actionCreators.searchBlur()); }, handleMouseEnter() { dispatch(actionCreators.mouseEnter()); }, handleMouseLeave() { dispatch(actionCreators.mouseLeave()); }, handleChangePage(page, totalPage, spin) { let originAngle = spin.style.transform.replace(/[^0-9]/ig, ''); if (originAngle) { originAngle = parseInt(originAngle, 10); }else { originAngle = 0; } spin.style.transform = 'rotate(' + (originAngle + 360) + 'deg)'; if (page < totalPage) { dispatch(actionCreators.changePage(page + 1)); }else { dispatch(actionCreators.changePage(1)); } }, logout() { dispatch(loginActionCreators.logout()) } } } export default connect(mapStateToProps, mapDispathToProps)(Header);
/src/common/header/style.js
import styled from 'styled-components'; import logoPic from '../../statics/logo.png'; export const HeaderWrapper = styled.div` z-index: 1; position: relative; height: 56px; border-bottom: 1px solid #f0f0f0; `; export const Logo = styled.div` position: absolute; top: 0; left: 0; display: block; width: 100px; height: 56px; background: url(${logoPic}); background-size: contain; `; export const Nav = styled.div` width: 960px; height: 100%; padding-right: 70px; box-sizing: border-box; margin: 0 auto; `; export const NavItem = styled.div` line-height: 56px; padding: 0 15px; font-size: 17px; color: #333; &.left { float: left; } &.right { float: right; color: #969696; } &.active { color: #ea6f5a; } `; export const SearchWrapper = styled.div` position: relative; float: left; .zoom { position: absolute; right: 5px; bottom: 5px; width: 30px; line-height: 30px; border-radius: 15px; text-align: center; &.focused { background: #777; color: #fff; } } `; export const NavSearch = styled.input.attrs({ placeholder: '搜索' })` width: 160px; height: 38px; padding: 0 30px 0 20px; margin-top: 9px; margin-left: 20px; box-sizing: border-box; border: none; outline: none; border-radius: 19px; background: #eee; font-size: 14px; color: #666; &::placeholder { color: #999; } &.focused { width: 240px; } &.slide-enter { transition: all .2s ease-out; } &.slide-enter-active { width: 240px; } &.slide-exit { transition: all .2s ease-out; } &.slide-exit-active { width: 160px; } `; export const SearchInfo = styled.div` position: absolute; left: 0; top: 56px; width: 240px; padding: 0 20px; box-shadow: 0 0 8px rgba(0, 0, 0, .2); background: #fff; `; export const SearchInfoTitle = styled.div` margin-top: 20px; margin-bottom: 15px; line-height: 20px; font-size: 14px; color: #969696; `; export const SearchInfoSwitch = styled.span` float: right; font-size: 13px; cursor: pointer; .spin { display: block; float: left; font-size: 12px; margin-right: 2px; transition: all .2s ease-in; transform-origin: center center; } `; export const SearchInfoList = styled.div` overflow: hidden; `; export const SearchInfoItem = styled.a` display: block; float: left; line-height: 20px; padding: 0 5px; margin-right: 10px; margin-bottom: 15px; font-size: 12px; border: 1px solid #ddd; color: #787878; border-radius: 3px; `; export const Addition = styled.div` position: absolute; right: 0; top: 0; height: 56px; `; export const Button = styled.div` float: right; margin-top: 9px; margin-right: 20px; padding: 0 20px; line-height: 38px; border-radius: 19px; border: 1px solid #ec6149; font-siz: 14px; &.reg { color: #ec6149; } &.writting { color: #fff; background: #ec6149; } `
/src/common/header/store/actionCreators.js
import * as constants from './constants'; import { fromJS } from 'immutable'; import axios from 'axios'; const changeList = (data) => ({ type: constants.CHANGE_LIST, data: fromJS(data), totalPage: Math.ceil(data.length / 10) }); export const searchFocus = () => ({ type: constants.SEARCH_FOCUS }); export const searchBlur = () => ({ type: constants.SEARCH_BLUR }); export const mouseEnter = () => ({ type: constants.MOUSE_ENTER }); export const mouseLeave = () => ({ type: constants.MOUSE_LEAVE }); export const changePage = (page) => ({ type: constants.CHANGE_PAGE, page }); export const getList = () => { return (dispatch) => { axios.get('/api/headerList.json').then((res) => { const data = res.data; dispatch(changeList(data.data)); }).catch(() => { console.log('error'); }) } };
/src/common/header/store/constants.js
export const SEARCH_FOCUS = 'header/SEARCH_FOCUS'; export const SEARCH_BLUR = 'header/SEARCH_BLUR'; export const CHANGE_LIST ='header/CHANGE_LIST'; export const MOUSE_ENTER = 'header/MOUSE_ENTER'; export const MOUSE_LEAVE = 'header/MOUSE_LEAVE'; export const CHANGE_PAGE = 'header/CHANGE_PAGE';
/src/common/header/store/index.js
import reducer from './reducer'; import * as actionCreators from './actionCreators'; import * as constants from './constants'; export { reducer, actionCreators, constants };
/src/common/header/store/
import * as constants from './constants'; import { fromJS } from 'immutable'; const defaultState = fromJS({ focused: false, mouseIn: false, list: [], page: 1, totalPage: 1 }); export default (state = defaultState, action) => { switch(action.type) { case constants.SEARCH_FOCUS: return state.set('focused', true); case constants.SEARCH_BLUR: return state.set('focused', false); case constants.CHANGE_LIST: return state.merge({ list: action.data, totalPage: action.totalPage }); case constants.MOUSE_ENTER: return state.set('mouseIn', true); case constants.MOUSE_LEAVE: return state.set('mouseIn', false); case constants.CHANGE_PAGE: return state.set('page', action.page); default: return state; } }
/src/statics/iconfont/iconfont.js
import { injectGlobal } from 'styled-components'; injectGlobal` @font-face { font-family: "iconfont"; src: url('./iconfont.eot?t=1528610804703'); /* IE9*/ src: url('./iconfont.eot?t=1528610804703#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), url('./iconfont.ttf?t=1528610804703') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ url('./iconfont.svg?t=1528610804703#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { font-family:"iconfont" !important; font-size:16px; font-style:normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `;