React16 全家桶学习笔记

1,617 阅读14分钟

写在最前面

本来是想在这次过年在家把 React 在重新复习下的,这次疫情言重放假时间也延长了,所以干脆把全家桶都重新刷了下。 记录个笔记,方便回头可以快速翻阅使用。

如有错误,还请大佬们支出,中国加油!武汉加油!

React学习路线

jspang.com/detailed?id…

  1. React16版基础视频【28集】
  2. Redux免费视频教程【24集】
  3. React Router免费视频教程【9集】
  4. React Hooks 免费视频教程【共11集】
  5. React服务端渲染框架Next.js入门(共12集)
  6. React Hooks+Egg.js博客实战视频教程(更新39集)
  7. 挑战全栈 Koa2免费视频教程 (共13集)

快速跳转

开始

  1. 国内镜像 npm config set registry https://registry.npm.taobao.org
  2. 全局安装 npm install -g create-react-app
  3. 项目安装 npx create-react-app my-app

插件

  1. React 代码快速生成 https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets
  2. Antd 代码快速生成 https://marketplace.visualstudio.com/items?itemName=bang.antd-snippets
  3. 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

  1. < 开头为 html
  2. { 开头为 js
  3. html 的 class 必须写成 className
  4. 设置 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

生命周期函数

截屏2021-04-05 下午1.02.02.png

WechatIMG210.jpeg

一. 初始化阶段

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 的升级版

截屏2021-05-07 下午8.56.14.png

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分离出来

  1. 子组件从父组件获取的方法,父bind,子不需要bind
  2. 子组件从父组件获取的方法,如果有参数,用箭头函数获取参数
    onClick={()=>{this.props.deleteItem(index)}
    
  3. 无状态组件,性能更好,适合大型项目

redux-thunk 中间件,放异步内容,可返回函数

npm install --save redux-thunk

配置 github.com/zalmoxisus/…

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

基本使用
  1. 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')
);
  1. 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);
  1. 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);
  1. 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}
/>
ReduxRouter 同时使用注意
render((
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
), document.getElementById('root'));

React Router

npm install --save react-router-dom

基本
  1. Router 包裹,推荐包裹在<App/>
    1. BrowserRouter 可以跟服务器配合最完善
    2. HashRouter 无法跟服务器配合
    3. 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')
    );
    
  2. Route 配置路由对应组件
    <Route path="/blog" exact component={Blog} />
    
    // 可以传参数
    <Route path="/blog" exact render={props=>(
        <Blog {...props} data="oy" />
    )} />
    
  3. 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

详情页如果配合后端获取数据,需要配合 componentDidMountcomponentDidUpdatecomponentWillUnmount 刷新页面,否则同一页面路由不会刷新。

路由跳转

本质上都是通过 history 实现的,本质上是个堆栈(堆盘子)。

  1. push pop
    this.props.history.push('/blog');
    
  2. 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>
ReduxRouter 同时使用注意
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

轻量级,服务器端渲染框架

  1. 全局安装 npm install -g create-next-app
  2. 项目安装 npx create-next-app my-app
  3. 启动 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)
路由监听事件
  1. routeChangeStart 路由将要变化(可用于loading动画等)
  2. beforeHistoryChange 发生变化
  3. routeChangeComplete 路由变化后
  4. routeChangeError 不含404
  5. hashChangeStart 当前页面路由锚点将要变化
  6. 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>
    )
axiospost 数据
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 文件引入
  1. npm install --save @zeit/next-css
  2. 根目录新建 next.config.js
    const withCSS = require('@zeit/next-css')
    if(typeof require !== 'undefined'){
        require.extensions['.css']=file=>{}
    }
    module.exports = withCSS({})
    
  3. 开始使用 import '../public/style.css'
使用 Antd
  1. npm install --save antd
  2. npm install --save babel-plugin-import
  3. 根目录新建 .babelrc
    {
        "presets": ["next/babel"],
        "plugins": [["import", { "libraryName": "antd","style":"css"}]]
    }
    
  4. 使用方法一样

打包包含 css 和 Antd 必须配置文件删除 css 按需加载

  1. .babelrc 删除 css 按需加载

    {
        "libraryName":"antd",
        // "style":"css"
    }
    
  2. pages 目录下新建 _app.js

    import App from 'next/app'
    import 'antd/dist/antd.css'
    
    export default App
    
  3. 必须重启

支持 scss

github.com/zeit/next-p…

  1. 安装 npm install --save @zeit/next-sass node-sass

  2. 根目录新建 next.config.js

    const withSass = require('@zeit/next-sass')
    module.exports = withSass({
      /* config options here */
    })
    
  3. 建立 styles.scss 文件,开始使用 scss

导出静态页面
  1. 图片必须 /images/ 开头
  2. 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' },
            }
        }
    })
    
  3. 路由参数
    '/product_view_1/index.html': { page: '/product_view', query: { id: '1' } },
    
百度统计
  1. baidu.js
    const baidu =`
        if(typeof document !== 'undefined') { 
            console.log('baidu');
            // 百度统计代码
        }
    `
    
    export default baidu
    
  2. 引入
    import baidu from '../public/baidu'
    
    <Head>
        ...
        <script dangerouslySetInnerHTML={{__html: baidu}}></script>
    </Head>
    

Koa2

基于 node.js 的 web 开发框架

安装

  1. npm init -y
  2. npm install --save koa
  3. 根目录新建 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')
    
  4. 终端输入 node index.js
  5. 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 的参数

  1. 安装 npm install --save koa-bodyparser
  2. 页面引用
    const bodyparser = require('koa-bodyparser')
    app.use(bodyparser())
    
  3. 页面获取 post 数据
    let pastData = ctx.request.body;
    ctx.body=pastData;
    

原生路由实现,步骤略复杂,看 demo05_router.js

使用“轮子”路由实现

  1. 安装 npm install --save koa-router
  2. 完整使用代码
    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

  1. 设置 ctx.cookies.set('MyName','OY')
  2. 读取 ctx.cookies.get('MyName')

模板引擎 ejs

  1. 安装 npm install --save ejs
  2. 安装 npm install --save koa-views
  3. 新建 view/index.ejs
  4. ejs 文件内的变量引用 <%= title %>
  5. 完整代码
    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')
    

访问静态资源

  1. 安装 npm install --save koa-static
  2. 配置访问静态资源
    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)))
    

实战项目

  1. 前台(Next.js、Antd)
  2. 接口(数据接口、业务逻辑、Egg.js的底层就是koa、RESTful)
  3. 后台(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 项目安装

  1. egg-init --type=sample
  2. npm i
  3. npm run dev

egg-cors模块,解决跨域问题

  1. 安装 npm install --save egg-cors
  2. 使用 config/plugin.js
    exports.cors = {
        enable: true,
        package: 'egg-cors'
    }
    
  3. 设置 config/config.default.js
    config.security = {
        csrf: {
            enable: false
        },
        domainWhiteList: [ '*' ]
    };
    config.cors = {
        origin: '*',
        allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
    };
    

egg-mysql模块

  1. 安装 npm install --save egg-mysql
  2. 使用 config/plugin.js
    exports.mysql = {
        enable: true,
        package: 'egg-mysql'
    }
    
  3. 数据库连接 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,
    };
    
  4. 连接 blog_content 表 let result = await ctx.app.mysql.select('blog_content');

路由守卫

  1. 用户登录验证
  2. 还在学习中...