写在最前面
本来是想在这次过年在家把 React 在重新复习下的,这次疫情言重放假时间也延长了,所以干脆把全家桶都重新刷了下。 记录个笔记,方便回头可以快速翻阅使用。
如有错误,还请大佬们支出,中国加油!武汉加油!
React学习路线
- React16版基础视频【28集】
- Redux免费视频教程【24集】
- React Router免费视频教程【9集】
- React Hooks 免费视频教程【共11集】
- React服务端渲染框架Next.js入门(共12集)
- React Hooks+Egg.js博客实战视频教程(更新39集)
- 挑战全栈 Koa2免费视频教程 (共13集)
快速跳转
开始
- 国内镜像
npm config set registry https://registry.npm.taobao.org 全局安装npm install -g create-react-app- 项目安装
npx create-react-app my-app
插件
- React 代码快速生成
https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets - Antd 代码快速生成
https://marketplace.visualstudio.com/items?itemName=bang.antd-snippets - Hooks 代码快速生成
https://marketplace.visualstudio.com/items?itemName=AlDuncanson.react-hooks-snippets
调试
- chrome:React Developer Tools
- chrome:Redux DevTools
Hello world
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {App} from './App'
ReactDOM.render(<App/>,document.getElementById('root'))
src/App.js
快捷: rce
import React, { Component } from 'react'
export class App extends Component {
render() {
return (
<div>
Hello world
</div>
)
}
}
export default App
JSX
<开头为 html{开头为 js- html 的 class 必须写成
className - 设置
Fragment可省略最外层 div
emmet 支持 JSX
在 settings.josn 添加
{
"emmet.includeLanguages": {
"javascript": "javascriptreact"
},
"emmet.triggerExpansionOnTab": true
}
添加组件数据
src/App.js
快捷: rconst
export class App extends Component {
constructor(props) {
super(props)
this.state = {
inputValue:'OY',
list:['PHP','JavaScript']
}
}
render() {
...
}
}
读取组件数据
{this.state.inputValue}
读取组件数组数据
<ul>
{
this.state.list.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
添加组件方法
src/App.js
export class App extends Component {
constructor(props) {
...
}
render() {
...
}
inputChange(e) {
console.log(e.target.value)
}
}
使用组件方法
{this.inputChange.bind(this)}
修改组件数据
src/App.js
快捷: sst
export class App extends Component {
constructor(props) {
...
}
render() {
...
}
inputChange(e) {
this.setState({
inputValue:e.target.value
})
}
}
数组添加
addList() {
this.setState({
list:[...this.state.list,this.state.inputValue]
})
}
数组删除,必须把数据保存到变量里
onClick={this.deleteItme.bind(this,index)}
deleteItme(index) {
let list = this.state.list
list.splice(index,1)
this.setState({
list:list
})
}
父向子组件传值和方法
传属性
<Xiaojiejieitem key={index} item={item} index={index} />
传方法
<Xiaojiejieitem deleteItme={this.deleteItme.bind(this)} />
子使用父组件传的值和方法
使用属性
<li>{this.props.item}</li>
使用方法
<li onClick={this.props.deleteItme.bind(this,this.props.index)}>{this.props.item}</li>
或者在子组件的方法内使用父组件的方法
handleClick() {
this.props.deleteItme(this.props.index)
}
数据类型校验
好习惯代码更健壮
快捷: impt
import PropTypes from 'prop-types'
校验和设置默认值
export class Xiaojiejieitem extends Component {
...
}
Xiaojiejieitem.propTypes = {
avname:PropTypes.string,
index:PropTypes.number,
deleteItme:PropTypes.func,
}
Xiaojiejieitem.defaultProps = {
avname:'OY',
}
ref
设置
<input ref = {(input)=>{this.input=input}} />
使用
this.input.value
生命周期函数
一. 初始化阶段
constructor() 是 ES6 的,不是 react 的生命周期函数
constructor() {
console.log('1 constructor')
}
二. 虚拟DOM挂载阶段
componentWillMount() 在 12.7 将被废除
componentWillMount() {
console.log('2-1 componentWillMount')
}
render() {
console.log('2-2 render')
}
componentDidMount() {
console.log('2-3 componentDidMount')
}
三. 更新阶段
componentWillUpdate() 在 12.7 将被废除
shouldComponentUpdate() {
console.log('3-1 shouldComponentUpdate')
return true
}
componentWillUpdate() {
console.log('3-2 componentWillUpdate')
}
render() {
console.log('3-3 render')
}
componentDidUpdate() {
console.log('3-4 componentDidUpdate')
}
只在使用了 props 的子组件有效,在 render() 之后 shouldComponentUpdate() 之前运行
componentWillReceiveProps() {
console.log('3-3.5 componentWillReceiveProps')
}
四. 卸载阶段
卸载删除时有效,在 componentWillReceiveProps() 之后 shouldComponentUpdate() 之前运行
componentWillUnmount() {
console.log('3-3.6 componentWillUnmount')
}
优化性能避免重复渲染
新版貌似不支持子组件 shouldComponentUpdate 返回 false ???
shouldComponentUpdate(nextProps,nextState) {
if(nextProps.item !== this.props.item){
return true
}else{
return false
}
}
使用 memo 实现必须要渲染
import React , {memo} from 'react';
import Child from './Child'
const ChildMemo = memo(Child)
<ChildMemo />
axios
-
npm install axios不会写入 package.json 中 -
npm install -g axios安装到全局 -
npm install -save axios(推荐)写入 package.json 的生产环境中 -
npm install -save-dev axios写入 package.json 的开发环境中
使用
import axios from 'axios'
componentDidMount() {
axios.post('http://127.0.0.1:8000/datamodel')
.then((res)=>{
console.log(res)
})
.catch((error)=>{
console.log(error)
})
}
写入state
.then((res)=>{
let list = []
for(let j in res.data){
list.push(res.data[j]['username'])
}
this.setState({
list:list
})
})
react-transition-group
npm install -save react-transition-group
使用 CSSTransition
import { CSSTransition } from 'react-transition-group'
<CSSTransition
in={this.state.isShow}
timeout={800}
classNames={'oy'}
>
<div>脑黄</div>
</CSSTransition>
CSS的6个状态
.oy-enter{}
.oy-enter-active{}
.oy-enter-done{}
.oy-exit{}
.oy-exit-active{}
.oy-exit-done{}
使用 TransitionGroup
import { CSSTransition,TransitionGroup } from 'react-transition-group'
<TransitionGroup>
{
this.state.list.map((item,index)=>{
return (
<CSSTransition
timeout={800}
classNames={'oy'}
unmountOnExit
key={index}
>
<Xiaojiejieitem key={index} item={item} index={index} deleteItme={this.deleteItme.bind(this)} />
</CSSTransition>
)
})
}
</TransitionGroup>
Ant Design
npm install --save antd
使用
import 'antd/dist/antd.css'
import { ... } from 'antd'
Redux
让 state 管理更方便,是 flux 的升级版
npm install --save redux
图书馆 src/store/index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
图书馆管理员 src/store/reducer.js
const initialState = {
inputValue : 'Write Someting',
list:[
'PHP',
'JavaScript',
]
}
export default (state = initialState, { type, payload }) => {
let newState = JSON.parse(JSON.stringify(state))
switch (type) {
case 'changeInput':
newState.inputValue = payload
return newState
case 'addItem':
newState.list.push(payload)
newState.inputValue = ''
return newState
case 'deleteItem':
newState.list.splice(payload,1)
return newState
default:
return state
}
}
获取
constructor(props) {
super(props)
this.state = store.getState()
}
借书者
changeInputValue(e) {
const action = {
type: 'changeInput',
payload:e.target.value
}
store.dispatch(action)
}
必须订阅 Redux 的状态,更新数据后才会再渲染
constructor(props) {
super(props)
store.subscribe(this.storeChange.bind(this))
}
storeChange() {
this.setState(store.getState())
}
优化:action type 常量化 src/store/actionTypes.js
快捷:rxconst
export const CHANGE_INPUT = 'changeInput'
export const ADD_ITEM = 'addItem'
export const DELETE_ITEM = 'deleteItem'
优化:action 复用化 src/store/actionFactory.js
快捷:rxaction
import { CHANGE_INPUT } from './actionTypes'
export const changeInputAction = (value) => ({
type: CHANGE_INPUT,
payload:value
})
使用:action 复用化
import { changeInputAction } from './store/actionCreators'
changeInputValue(e) {
const action = changeInputAction(e.target.value)
store.dispatch(action)
}
Redux进阶
UI分离出来
- 子组件从父组件获取的方法,父bind,子不需要bind
- 子组件从父组件获取的方法,如果有参数,用箭头函数获取参数
onClick={()=>{this.props.deleteItem(index)} - 无状态组件,性能更好,适合大型项目
redux-thunk 中间件,放异步内容,可返回函数
npm install --save redux-thunk
import { createStore,applyMiddleware,compose } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
))
export default store
redux-saga 中间件,也比较绕...
npm install --save redux-saga
react-redux
在redux基础上才能使用,简化了原生redux写法
npm install --save redux react-redux
基本使用
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
src/store.js
import {createStore} from 'redux';
function user(state={user: 'oldyellow', age: '18'}, action){
switch(action.type){
case 'change_name':
return {
...state,
user: action.n,
};
case 'set_age':
return {
...state,
age: parseInt(state.age)+action.n,
};
default:
return state;
}
}
export default createStore(user);
src/App.js父组件可以获取子数据,其实都在redux里
import {connect} from 'react-redux';
import User from './User';
function App(props) {
return (
<div className="App">
<h1>Hello {props.user}</h1>
<User />
</div>
);
}
export default connect((state, props)=>Object.assign({}, props, state))(App);
src/User.js子组件可以获取和更新数据,其实都是通过redux实现的
import React from 'react'
import {connect} from 'react-redux';
function User(props) {
const change = () => {
props.change('OY');
}
const add = () => {
props.addAge(1);
}
return (
<div>
<h2>User</h2>
<p>用户名:{props.user} <button type="button" onClick={change}>Change</button></p>
<p>年龄:{props.age} <button type="button" onClick={add}>+1</button></p>
</div>
)
}
export default connect((state, props)=>Object.assign({}, props, state),{
change(n){
return{
type:'change_name',
n
}
},
addAge(n){
return{
type:'set_age',
n
}
}
})(User);
redux 的数据是绑定在 props 上的
<Input
placeholder={this.props.inputValue}
/>
方法,无需 bind(this)
<Input
onChange={this.props.inputChange}
/>
Redux 和 Router 同时使用注意
render((
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
), document.getElementById('root'));
React Router
npm install --save react-router-dom
基本
Router包裹,推荐包裹在<App/>外BrowserRouter可以跟服务器配合最完善HashRouter无法跟服务器配合MemoryRouter对地址完全不修改一刷新就没
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import {BrowserRouter as Router} from 'react-router-dom'; ReactDOM.render( <Router> <App /> </Router>, document.getElementById('root') );Route配置路由对应组件<Route path="/blog" exact component={Blog} />// 可以传参数 <Route path="/blog" exact render={props=>( <Blog {...props} data="oy" /> )} />Link链接<Link to="/blog">Blog</Link><Link to={{ pathname:'/blog', search:'?kw=oy', hash:'#oy' }}>Blog</Link>
开始使用
快捷 imrr
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
src/AppRouter.js
import React from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import App from './App'
import Blog from './Blog'
function AppRouter(){
return (
<Router>
<ul style={{padding:'10px'}}>
<li><Link to="/">Index</Link></li>
<li><Link to="/blog">Blog</Link></li>
</ul>
<Route path="/" exact component={App} />
<Route path="/blog" component={Blog} />
</Router>
)
}
export default AppRouter
动态传值并获取传的值
动态传值
<Router>
<div style={{padding:'10px'}}>
<Button><Link to="/blog/123">Blog</Link></Button>
</div>
<Route path="/blog/:id" component={Blog} />
</Router>
获取传的值
this.props.match.params.id
详情页如果配合后端获取数据,需要配合 componentDidMount 和 componentDidUpdate 和 componentWillUnmount 刷新页面,否则同一页面路由不会刷新。
路由跳转
本质上都是通过 history 实现的,本质上是个堆栈(堆盘子)。
pushpopthis.props.history.push('/blog');replace
Redirect 重定向
import { Redirect } from 'react-router-dom'
<Redirect to="/home/" />
编程式重定向
constructor(props) {
super(props)
this.state = {}
this.props.history.push("/home/")
}
二级嵌套路由
二级嵌套路由,父路由不建议使用 exact
<Router>
<Route path="/admin/" component={Admin} />
</Router>
admin.js 二级嵌套路由
<Linkk to="/admin/">Admin</Link>
<Linkk to="/admin/blog">blog</Link>
<Linkk to="/admin/List">List</Link>
<Route path="/admin/" exact component={Home} />
<Route path="/admin/blog" exact component={Blog} />
<Route path="/admin/list" exact component={List} />
二级路由避免制造耦合
let { path } = this.props.match;
<Linkk to={`${path}/`}>Admin</Link>
<Linkk to={`${path}/blog`}>blog</Link>
<Linkk to={`${path}/list`}>List</Link>
<Route path={`${path}/`} exact component={Home} />
<Route path={`${path}/blog`} exact component={Blog} />
<Route path={`${path}/list`} exact component={List} />
动态读取路由配置
let routerConfig = [
{path:'/',title:'Index',exact:true,component:App},
{path:'/photo',title:'Photo',exact:false,component:Photo},
{path:'/video',title:'Video',exact:true,component:Video}
]
<Router>
<div>
{
routerConfig.map((item,index)=>{
return (
<Button key={index}><Link to={item.path}>{item.title}</Link></Button>
)
})
}
</div>
<div>
{
routerConfig.map((item,index)=>{
return (
<Route key={index} path={item.path} exact={item.exact} component={item.component} />
)
})
}
</div>
</Router>
Redux 和 Router 同时使用注意
render((
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
), document.getElementById('root'));
React hooks
- React版本必须16.8+,无需特意安装hooks。
- hooks是一套工具函数的集合,为了增强
无状态组件/函数组件的功能 无状态组件/函数组件专用- hooks工作原理 === reducer工作原理
使用限制
- 只用在
无状态组件/函数组件上 - 一定要放在
无状态组件/函数组件的第一层
无状态组件/函数组件 rfce
import React from 'react'
function Demo() {
return (
<div>
</div>
)
}
export default Demo
状态 ush
- 规范使用
const来声明 - React 的状态在渲染过程中不会立刻改变,所有的修改先存着,等渲染完成后触发重新渲染。
- 如果设置重复一样的值,只会再渲染一次。
import React, { useState } from 'react'
function App() {
const [count,setCount] = useState(0)
return (
<div>
<p>Your clicked : {count}</p>
<button onClick={()=>{setCount(count+1)}}>Click me</button>
</div>
)
}
export default App
影响、效果、副作用 ueh
- 延迟执行,渲染完成后再执行,用过外部数据读取
- 移除清理,定时回收,避免内存
Memory Leak泄漏 useEffect第二个参数[],监听指定状态值变化才执行
useEffect = componentDidMount + componentDidUpdate
import React, { useState,useEffect } from 'react'
const [count,setCount] = useState(0)
useEffect(() => {
console.log(count)
});
useEffect = componentDidMount 只运行一次,之后不再运行
import React, { useState,useEffect } from 'react'
const [count,setCount] = useState(0)
useEffect(() => {
console.log(count)
},[]);
useEffect 实现生命周期函数的 componentWillUnmount
return 只允许返回函数,其他不行
useEffect(() => {
return ()=>{
console.log('componentWillUnmount');
}
},[]);
// 移除回收
useEffect(()=>{
let timer = setTimeout(()=>{
// setState
},2000);
// 运行完后回收
return ()=>clearTimeout(timer);
});
useEffect 结合 axios 只运行一次获取数据
import React,{useState,useEffect} from 'react'
const [nav, setNav] = useState([]);
useEffect(() => {
axios('http://127.0.0.1:7001/index/getType')
.then(
(res)=>{
setNav(res.data.data)
}
)
},[]);
useEffect 结合 axios + async
import React,{useState,useEffect} from 'react'
const [nav, setNav] = useState([]);
useEffect(() => {
const fetchData = async ()=>{
const result= await axios('http://127.0.0.1:7001/index/getType')
.then(
(res)=>{
setNav(res.data.data)
}
)
}
fetchData()
},[]);
1个页面用2个 useEffect
const getTest = () => {
// axios
// refresh 变化
}
// 只运行一次
useEffect(() => {
getTest()
},[])
// 每次 refresh 更新 运行一次
useEffect(() => {
if(refresh){
getTest()
}
},[refresh])
引用 urh
useRef实现ref功能- 渲染完后才能使用,可以搭配
useEffect使用 useRef不可以在class组件里用,可以用createRef
// 设定
function (){
const txt = useRef();
return <div ref = {txt}></div>;
}
// 使用
txt.current.value = 'oyoyoy';
手动实现 ref,16.3版之前 class 组件里用
render(){
return (
<>
<span ref={element=>{
this.a=element;
}}>a</span>
<button type="button" onClick={()=>{
this.a.style.background='yellow';
}}>按钮</button>
</>
);
}
传递引用 forwardRef
- 函数组件无法实例化,所以无法使用
ref,必须通过forwardRef来指定 - 方便函数组件对外暴露方法和属性
// Cmp1.js 设定
const Cmp1 = React.forwardRef((props, ref) => (
return (
<div>
<button ref={ref}>按钮</button>
<div>容器</div>
</div>
)
))
// App.js 使用
<Cmp1 ref={cmp1} />
自定义组件希望暴露的内容
// Cmp1.js 设定
const Cmp1 = React.forwardRef((props, ref) => (
ref.current={
a: 18,
show(){
alert(this.a);
}
};
return (
<div>
<button>按钮</button>
</div>
)
))
// App.js 使用
<Cmp1 ref={cmp1} />
cmp1.current.a // 输出18
上下文 context
- 父向子传值
- 父级定义,子级全部能获取,无需一个个定义参数
- 父组件用
createContext子组件用useContext
公共
// theme.js
import {createContext} from 'react';
export default createContext('light');
父组件
import React from, {useState, useEffect} 'react';
import Theme from './theme';
import Header from './header';
function Bodyer() {
const [theme, setTheme]=useState('light');
useEffect(()=>{
let timer = setTimeout(()=>{
setTheme('green');
},2000);
// 运行完后回收
return ()=>clearTimeout(timer);
});
return (
<Theme.Provider value={theme}>
<Header/>
</Theme.Provider>
)
}
子组件
import React from 'react';
import Theme from './theme';
function Header(props) {
const theme=useContext(Theme);
return (
<div>
{theme}
</div>
)
}
父传值 Demo2
import React, { useState,createContext } from 'react'
import Blog from './Blog'
export const CountContext = createContext()
function App() {
const [count,setCount] = useState(7)
return (
<CountContext.Provider value={count}>
<Blog />
</CountContext.Provider>
)
}
export default App
子取值 Demo2
import React, { useContext } from 'react'
import { CountContext } from './App'
function Blog(){
const count = useContext(CountContext)
return (
<div>
Blog:{count}
</div>
)
}
export default Blog
同一页面组件传值
const DiffDate = (e) => {
console.log(e.endDate)
}
<DiffDate endDate="2020-02-02" />
useReducer urdh
- 所有
hooks的祖宗 - 和
redux基本一模一样 - 一般用不到,主要用在自定义
hook上
import React,{ useReducer } from 'react'
const [myname, dispatch] = useReducer((state,action)=>{
switch (action.type) {
case 'oldyellow':
return state='oldyellow'
default:
return state
}
}, 'oy')
<h1>{myname}</h1>
<button onClick={()=>{dispatch('oldyellow')}}>Change</button>
自定义 hook
use开头的函数- 内部就可以使用
hook的函数
useMemo umh
解决父渲染子组件重复渲染,实践中还没搞懂,后续再研究
useCallback ucbh
函数命名规范:use**(){}
router 常用 hook
-
useHistory
history实例——操作集合:push、replace、back、... -
useLocation
localtion实例——各种信息、path、query、params... -
useParams
params实例——路由参数 -
useRouteMatch
提取路由信息(不跳转)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {BrowserRouter as Router} from 'react-router-dom';
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
App.js
import React from 'react';
import {Route, Switch, Link, useHistory} from 'react-router-dom';
import News from './components/news';
function App() {
// const history = useHistory();
return (
<div>
<Link to="/news/1">新闻1</Link>
<Link to="/news/2">新闻2</Link>
<Switch>
<Route path="/news/:id" component={News} />
</Switch>
</div>
)
}
News.js
import React from 'react';
import {useParams} from 'react-router-dom';
function News(props) {
const {id}=useParams();
return (
<div>
news: {id}
</div>
)
}
redux 常用 hook
- 通过 hook 让 redux 使用更简化了
useSelector获取数据useDispatch修改数据
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
const store=createStore((state={name: 'blue', age: 18}, action)=>{
switch(action.type){
case 'setName':
return {
...state,
name: action.value
};
default:
return state;
}
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.js
import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
function Demo() {
const name=useSelector(state=>{
return state.name;
});
const dispatch=useDispatch();
return (
<div>
name: {name}
<button type="button" onClick={()=>{
dispatch({type: 'setName', value: 'oldyellow'})
}}>按钮</button>
</div>
);
}
Next.js
轻量级,服务器端渲染框架
- 全局安装
npm install -g create-next-app - 项目安装
npx create-next-app my-app - 启动
npm run dev
新建页面 rfce
pages/Oldyellow.js
import React from 'react'
function Oldyellow() {
return (
<div>
</div>
)
}
export default Oldyellow
访问 http://localhost:3000/oldyellow
新建组件(一样) rfce
components/Jerry.js
import React from 'react'
function Jerry() {
return (
<div>
Jerry
</div>
)
}
export default Jerry
页面引用组件
import Jerry from '../components/Jerry'
<Jerry />
路由
使用 <Link>
import Link from 'next/link'
<Link href="/link1">link1</Link>
使用 Router
import Router from 'next/router'
<li onClick={()=>{Router.push('/link1')}}>link1</li>
路由传递参数
使用 query 传递参数
使用 <Link> 传递参数的2种方法
<li><Link href="/link3?id=1&name=oy">Link-link1</Link></li>
<li><Link href={{pathname:'/link3',query:{id:2,name:'oy2'}}}>Link-link1</Link></li>
使用 Router 传递参数的2种方法
<li onClick={()=>{Router.push('/blog?id=1&name=oy')}}>link2</li>
<li onClick={()=>{Router.push({pathname:'/blog',query:{id:1,name:'oy'}})}}>link3</li>
路由别名
<li onClick={()=>{Router.push({pathname:'/blog',query:{id:1,name:'oy'}},'/blog_1_oy')}}>link3</li>
路由获取参数
获取参数 withRouter
import React from 'react'
import { withRouter } from 'next/router'
function blog({router}) {
return (
<div>
<p>{router.query.id}</p>
<p>{router.query.name}</p>
</div>
)
}
export default withRouter(blog)
路由监听事件
- routeChangeStart 路由将要变化(可用于loading动画等)
- beforeHistoryChange 发生变化
- routeChangeComplete 路由变化后
- routeChangeError 不含404
- hashChangeStart 当前页面路由锚点将要变化
- hashChangeComplete 当前页面路由锚点变化后
使用方式
Router.events.on('routeChangeStart',(...arg)=>{
console.log('1.routeChangeStart',...arg);
})
Router.events.on('beforeHistoryChange',(...arg)=>{
console.log('2.beforeHistoryChange',...arg);
})
Router.events.on('routeChangeComplete',(...arg)=>{
console.log('3.routeChangeComplete',...arg);
})
Router.events.on('routeChangeError',(...arg)=>{
console.log('4.routeChangeError',...arg);
})
Router.events.on('hashChangeStart',(...arg)=>{
console.log('5.hashChangeStart',...arg);
})
Router.events.on('hashChangeComplete',(...arg)=>{
console.log('6.hashChangeComplete',...arg);
})
使用 'axios'
必须使用 getInitialProps ,并且必须是通过路由跳转过去的页面才会生效。。。
import axios from 'axios'
index.getInitialProps = async ()=>{
const promise =new Promise((resolve)=>{
axios('http://127.0.0.1:8000/datamodel').then(
(res)=>{
console.log('远程数据结果:',res)
resolve({items:res.data})
}
)
})
return await promise
}
读取数据
function index4({items}) {
return (
<div>
{
items.map((item,index)=>{
return (
<p key={index}>{item.id}:{item.username} {item.email}</p>
)
})
}
</div>
)
}
获取 get 参数使用 context
Lists.getInitialProps = async (context)=>{
let id = context.query.id
const promise =new Promise((resolve)=>{
axios('http://127.0.0.1:7001/index/getListById/'+id).then(
(res)=>{
console.log('远程数据结果:',res)
resolve({items:res.data.data})
}
)
})
return await promise
}
css
return (
<div>
<h2>OY</h2>
<style jsx>
{`
h2{color:yellow}
`}
</style>
</div>
)
axios 来 post 数据
let dataProps = {
'user_name':userName,
'password':password,
}
axios({
method:'post',
url:'http://localhost:3000/admin/loginCheck',
data:dataProps,
withCredentials:true// 跨域 cookie 支持
}).then(
(res)=>{
setLoging(false)
console.log(res.data)
if(res.data.data=='登录成功'){
sessionStorage.setItem('isLogin',true)
props.history.push('/admin/')
}else{
message.error('用户名密码错误')
}
}
)
懒加载外部模块
const resetTime = async () => {
const moment = await import('moment')
setTimes(moment.default().format('YYYY年MM月DD'))
}
懒加载自定义组件,渲染才会读取
import dynamic from 'next/dynamic'
const Jerry = dynamic(import('../components/Jerry'))
<Jerry/>
支持 css 文件引入
npm install --save @zeit/next-css- 根目录新建 next.config.js
const withCSS = require('@zeit/next-css') if(typeof require !== 'undefined'){ require.extensions['.css']=file=>{} } module.exports = withCSS({}) - 开始使用
import '../public/style.css'
使用 Antd
npm install --save antdnpm install --save babel-plugin-import- 根目录新建 .babelrc
{ "presets": ["next/babel"], "plugins": [["import", { "libraryName": "antd","style":"css"}]] } - 使用方法一样
打包包含 css 和 Antd 必须配置文件删除 css 按需加载
-
.babelrc 删除 css 按需加载
{ "libraryName":"antd", // "style":"css" } -
pages 目录下新建 _app.js
import App from 'next/app' import 'antd/dist/antd.css' export default App -
必须重启
支持 scss
-
安装
npm install --save @zeit/next-sass node-sass -
根目录新建
next.config.jsconst withSass = require('@zeit/next-sass') module.exports = withSass({ /* config options here */ }) -
建立 styles.scss 文件,开始使用 scss
导出静态页面
- 图片必须
/images/开头 - next.config.js 配置
const withSass = require('@zeit/next-sass') module.exports = withSass({ exportPathMap: async function (defaultPathMap) { return { '/': { page: '/' }, '/core/index.html': { page: '/core' }, '/history/index.html': { page: '/history' }, '/life/index.html': { page: '/life' }, '/product/index.html': { page: '/product' }, '/school/index.html': { page: '/school' }, '/shop/index.html': { page: '/shop' }, '/product_view_1/index.html': { page: '/product_view_1' }, '/product_view_2/index.html': { page: '/product_view_2' }, '/product_view_3/index.html': { page: '/product_view_3' }, '/product_view_4/index.html': { page: '/product_view_4' }, } } }) - 路由参数
'/product_view_1/index.html': { page: '/product_view', query: { id: '1' } },
百度统计
- baidu.js
const baidu =` if(typeof document !== 'undefined') { console.log('baidu'); // 百度统计代码 } ` export default baidu - 引入
import baidu from '../public/baidu' <Head> ... <script dangerouslySetInnerHTML={{__html: baidu}}></script> </Head>
Koa2
基于 node.js 的 web 开发框架
安装
npm init -ynpm install --save koa- 根目录新建 index.js
const Koa = require('koa') const app = new Koa() app.use(async(ctx)=>{ ctx.body = 'Hello world' }) app.listen(3000) console.log('app is running') - 终端输入
node index.js - http://localhost:3000/
获取 get 的参数
http://localhost:3000/?id=1&age=18
let ctx_query = ctx.query
let ctx_querystring = ctx.querystring
ctx.body = {
ctx_query,
ctx_querystring
}
原生获取 post 的参数,步骤略复杂,看 demo03_post.js
使用“轮子”获取 post 的参数
- 安装
npm install --save koa-bodyparser - 页面引用
const bodyparser = require('koa-bodyparser') app.use(bodyparser()) - 页面获取 post 数据
let pastData = ctx.request.body; ctx.body=pastData;
原生路由实现,步骤略复杂,看 demo05_router.js
使用“轮子”路由实现
- 安装
npm install --save koa-router - 完整使用代码
const Koa = require('koa'); const Router = require('koa-router'); const app = new Koa(); const router = new Router(); router .get('/',(ctx,next)=>{ ctx.body = "Index" }) .get('/blog',(ctx,next)=>{ ctx.body = "Blog" }) .get('/page',(ctx,next)=>{ let ctx_query = ctx.query ctx.body = ctx_query }) app .use(router.routes()) .use(router.allowedMethods()) app.listen(3000,()=>{ console.log('running'); });
cookie
- 设置
ctx.cookies.set('MyName','OY') - 读取
ctx.cookies.get('MyName')
模板引擎 ejs
- 安装
npm install --save ejs - 安装
npm install --save koa-views - 新建 view/index.ejs
- ejs 文件内的变量引用
<%= title %> - 完整代码
const Koa = require('koa') const views = require('koa-views') const path = require('path') const app = new Koa() app.use(views(path.join(__dirname,'./view'),{ extension:'ejs' })) app.use(async(ctx)=>{ let title = 'Hello OY' await ctx.render('index',{title}) }) app.listen(3000) console.log('app is running')
访问静态资源
- 安装
npm install --save koa-static - 配置访问静态资源
const Koa = require('koa') const path = require('path') const static = require('koa-static') const app = new Koa() const staticPath = './static' app.use(static(path.join(__dirname,staticPath)))
实战项目
- 前台(Next.js、Antd)
- 接口(数据接口、业务逻辑、Egg.js的底层就是koa、RESTful)
- 后台(Hooks、Antd)
- react markdown
npm install --save react-markdown - markdown navbar
npm install --save markdown-navbar - marked + highlight.js
- Egg.js 全局安装
npm i egg-init -g
Egg.js 项目安装
egg-init --type=samplenpm inpm run dev
egg-cors模块,解决跨域问题
- 安装
npm install --save egg-cors - 使用 config/plugin.js
exports.cors = { enable: true, package: 'egg-cors' } - 设置 config/config.default.js
config.security = { csrf: { enable: false }, domainWhiteList: [ '*' ] }; config.cors = { origin: '*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS' };
egg-mysql模块
- 安装
npm install --save egg-mysql - 使用 config/plugin.js
exports.mysql = { enable: true, package: 'egg-mysql' } - 数据库连接 config/config.default.js 追加
config.mysql = { // database configuration client: { // host host: 'mysql.com', // port port: '3306', // username user: 'test_user', // password password: 'test_password', // database database: 'test', }, // load into app, default is open app: true, // load into agent, default is close agent: false, }; - 连接 blog_content 表
let result = await ctx.app.mysql.select('blog_content');
路由守卫
- 用户登录验证
- 还在学习中...