nextjs ssr渲染

1,617 阅读6分钟

next.js使用

路由映射

as的功能,在Link后面加上as可以改变url显示的方式,但是原有的功能不会改变,路径的可选修饰符,该修饰符将显示在浏览器URL栏中。

router.push也是可以使用as功能,在push第二个参数改变一下

nextjs实际上面没有改变文件结构,所以直接访问是as修改url后的路径是访问不到的.使用koa可以解决这个问题 const handle = app.getRequestHandler()

image-20200301224309573

Router的钩子

路由的事件有6种,分别是

image-20200301224414952

const events = [
  'routerChangeStart',
  'routerChangeComplete',
  'routerChangeError',
  'beforeHistoryChange',
  'hashCahngeStart',
  'hashChangeComplete'
]
function makeEvent(type){
  return (...args)=>{
    console.log(type,...args);
  }
}
events.forEach(event=>{
  Router.events.on(event,makeEvent(event))
})

hashChange是带#的跳转

historyChange是不带#的跳转

getInitialProps

这个方法是nextjs特有的方法,可以在页面中获取数据,仅有pages文件夹下的js文件才会调用这个方法.在服务端就会调用这个方法,不用等到浏览器(客户端)来渲染.但是客户端也偶尔会执行这段代码,当路由跳转到这个页面就是客服端执行.如果是直接打开这个页面就是服务端执行

const A= ({router,name})=><Link href="/test/test" as="test"><div><Button>123456</Button><Comp prop="132">123{router.query.id}+{name}</Comp></div></Link>
A.getInitialProps=()=>{
  return {  //return 的所有东西都会当作A组件的props属性供A组件使用
    name:'joke'
  }
}

nextjs自定义app _app.js

import  App,{Container} from 'next/app'
import 'antd/dist/antd.css'
class myApp extends App{

  static async getInitialProps({Component,ctx}){//每次页面切换都会调用这个方法
    co
    let pageProps;
    if(Component.getInitialProps){
      pageProps = await Component.getInitialProps(ctx)
    }
    return {
      pageProps
    } 
  }

  render(){
    const {Component,pageProps} = this.props;
    console.log(Component);
    return(
      <Container>
        <Component {...pageProps}></Component>
      </Container>
    )
  }
}
export default myApp

自定义_app.js 也有getInitialProps方法,这个方法会传入其他组件作为参数.组件内部也有getInitialProps,这里自定义组件复原了如果组件如果有getInitialProps的方法的内容就把他返回,供组件使用props,不然组件接受不到props.默认的app组件也是这样去做的

nextjs自定义document

image-20200302114817904

import Document,{ Html,Head,Main,NextScript } from 'next/document';
class MyDocument extends Document{

  static async getInitialProps(ctx){ //要么不覆盖,覆盖的话就要把原来的props返回出去
    const props = await Document.getInitialProps(ctx)
    return {...props}
  }

  render(){
    return (
      <Html>
        <Head>
          <style>
            {`.test {color:red}`}
          </style>
        </Head>
        <body className="test">
          <Main></Main>
          <NextScript></NextScript>
        </body>
      </Html>
    )
  }
}
export default MyDocument

next定义样式

局部样式

<style jsx>{`//会自动隔离,在其他组件不生效
    a{
      color:blue
    }
    .link{
      color:yellow
    }
  `}
</style>

全局样式

<style jsx global>{`//全局生效,不推荐,因为组件卸载之后样式也会消失,全局样式和局部样式可以共存,多加一个style就可以
    a{
      color:blue
    }
    .link{
      color:yellow
    }
  `}
</style>

css-in-js

import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
function withLog(Comp) {
  return props => {
    console.log(props);
    return <Comp {...props} />;
  };
}

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    //要么不覆盖,覆盖的话就要把原来的props返回出去
    const originalRenderPage = await ctx.renderPage;
    const sheet = new ServerStyleSheet();
    try {
      ctx.renderPage = () => {
        return originalRenderPage({
          enhanceApp: App => props =>
            sheet.collectStyles(<App {...props}></App>)
        });
      };
      const props = await Document.getInitialProps(ctx);
      return { ...props,style:<>{props.styles}{sheet.getStyleElement()}</> };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head>
          <style>{`.test {color:red}`}</style>
        </Head>
        <body className="test">
          <Main></Main>
          <NextScript></NextScript>
        </body>
      </Html>
    );
  }
}
export default MyDocument;

.babelrc中添加如下配置

image-20200302172102523

react hooks

function组件通过hooks新增了保存state和更新state的能力,通过API 分别是useState和useEffect

useReduce,useReduce是更新复杂状态比较方便

useState,useEffect

import React, { useState, useEffect, useReducer } from "react";

function MyCountFunction() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("jojo");
  // useEffect(() => {
  //   //组件渲染完成会触发这个回调
  //   const interval = setInterval(() => {
  //     // setCount(2); //这样是将count直接赋值为2
  //     setCount(c => c + 1); //这样是将count作为形参传入,更新count值为返回的结果
  //   }, 1000);
  //   return () => clearInterval(interval); //返回的函数会在组件卸载时候调用
  // }, []);
  useEffect(()=>{
    console.log('effect invoked')
    return ()=>console.log('effect deteched');
  },[])//如果传入name就会在name每次变化时触发useEffect的两个函数,同理count也是一样
  return (
    <div>
      <span> {count} </span>
      <input type="text" value={name} onChange={e => setName(e.target.value)} />
      <div>{name}</div>
    </div>
  );
}

function countReducer(state, action) {
  switch (action.type) {
    case "add":
      return state + 1;

    case "minus":
      return state - 1;

    default:
      return state;
  }
}

function ReduceCountComponent() {
  const [count, dispatchCount] = useReducer(countReducer, 0);
  useEffect(() => {
    //组件渲染完成会触发这个回调
    const interval = setInterval(() => {
      dispatchCount({ type: "add" });
    }, 1000);
    return () => clearInterval(interval); //返回的函数会在组件卸载时候调用
  }, []);
  return <span> {count} </span>;
}

export default MyCountFunction;

useref

useRef使得函数组件也能实现只有component组件才能实现的API

usecontext

image-20200303235932202

image-20200304000059063

image-20200304000123186

直接使用context就是值

useCallback

const handleSearchChange = useCallback((event)=>{
    setSearch(event.target.value)
  },[setSearch]) //由于setSearch不会改变 所以数组内不填也可以

useCallback 的作用在于利用 memoize 减少无效的 re-render,来达到性能优化的作用。还是那句老生常谈的话,“不要过早的性能优化”。从实际开发的经验来看,在做这类性能优化时,一定得观察比较优化的结果,因为某个小角落的 callback 就可能导致优化前功尽弃,甚至是适得其反。

redux

import { createStore } from 'redux';
const initialState = {
  count :0
}
const add = 'add'
function reducer(state = initialState,action){
  console.log(state,action);
  switch (action.type) {
    case add:
    return {count:state.count+1}
    default:
      return state
  }
}
const store = createStore(reducer,initialState)
store.dispatch({type:'add'})
store.subsribe(()=>{//每次在数据变化时会调用这个函数,可以在react中更新状态重新渲染
  console.log(store.getState());
})
console.log(store.getState());

export default store

reducer

应该返回对象而不是返回一个普通字面量,这样才能更新组件

image-20200303221402599

conbineReducers时,initialState也要合并

import { createStore, combineReducers } from "redux";
const initialState = {
  count: 0
};
const add = "add";
function reducer(state = initialState, action) {
  console.log(state, action);
  switch (action.type) {
    case add:
      return { count: state.count + 1 };
    default:
      return state;
  }
}
function userReducer(state = { username: "zhangsan" }, action) {
  switch (action.type) {
    case "changeName":
      return { username: action.name };
    default:
      return state;
  }
}
const allReducer = combineReducers(reducer,userReducer)
const store = createStore(allReducer, {count:initialState,user:{username:'zhangsan'}});
store.dispatch({ type: "add" });
store.subsribe(() => {
  //每次在数据变化时会调用这个函数,可以在react中更新状态重新渲染
  console.log(store.getState());
});
console.log(store.getState());

export default store;

action

import { createStore, combineReducers,applyMiddleware } from "redux";
import ReduxThunk from 'redux-thunk'

const store = createStore(allReducer, {count:initialState,user:{username:'zhangsan'}},applyMiddleware(ReduxThunk));//applyMiddleware(ReduxThunk)使得action的dispatch内可以传入异步的请求数据
store.dispatch(异步数据);
import { createStore, combineReducers,applyMiddleware } from "redux";
import ReduxThunk from 'redux-thunk'
const initialState = {
  count: 0
};
const add = "add";
function reducer(state = initialState, action) {
  console.log(state, action);
  switch (action.type) {
    case add:
      return { count: state.count + action.num || 1 };
    default:
      return state;
  }
}
function userReducer(state = { username: "zhangsan" }, action) {
  switch (action.type) {
    case "changeName":
      return { username: action.name };
    default:
      return state;
  }
}
function addSync(num){
  return (dispatch,getState)=>{ //如果是给store.dispatch调用会传入两个参数就是dispatch,getState
    setTimeout(()=>{
      dispatch({type:'add',num})
    },1000)
  }
}
const allReducer = combineReducers({counter:reducer,user:userReducer})//合并要传入一个对象
const store = createStore(allReducer, {counter:initialState,user:{username:'zhangsan'}},applyMiddleware(ReduxThunk));//applyMiddleware(ReduxThunk)使得action的dispatch内可以传入异步的请求数据,第二个参数是状态的对象,要和第一个对象的键对应
store.dispatch({ type: "add" });
store.dispatch({ username: "lisi",type:'changName' });
store.dispatch(addSync(5))
store.subscribe(() => {
  //每次在数据变化时会调用这个函数,可以在react中更新状态重新渲染
  console.log(store.getState());
});
console.log(store.getState());

export default store;

异步请求有可能出现一点警告如下

image-20200305114931071

react-redux

redux原本是独立于react的,redux有连接react的一种方案

  1. 要用provider在_app里面

  2. provider的作用是使provider下面包裹的元素能够访问provider里面提供的值也就是store

  3. 怎么使用provider

    1. image-20200303231645963

    2. 在index里面引入connect组件image-20200303231816401

    3. 在export default的地方connect()(组件名) connect()返回一个方法,再调用这个方法所以是两个括号

      export default connect(
        function mapStateToProps(state){
        return { //把一些state转化为props传给A组件,可以在A的props获取值
          count:state.counter.count,
          username:state.user.username
        }
      },function mapDispatchToProps(dispatch){
        return {
          add:(num)=>dispatch({type:'add',num}),//这里不推荐直接用字符串做type
          rename:(name)=>{dispatch({type:'changeName',name:name})}
        }
      })(withRouter(A)) ;
      
      
   
   
   ## react中引入redux

现在在chrome调试工具就能够看到redux选项卡

## nextjs中的HOC  (HOC是接受组件作为参数并返回新的组件)

​```javascript
export default (Comp)=>{ //接受组件返回组件,可以对特定参数进行处理,
  return function TestHocComo({name,otherprops}){
    const name = name + 123;
    return <Comp {...otherprops} name={name}></Comp>
  }
}

在redux中把props改变

export default (Comp)=>{
  function TestHocComo({Component,pageProps,...rest}){ //配合_app来使用的话可以接收Component pageProps
    // const name = name + 123;
    console.log(Component,pageProps);
    pageProps&&(pageProps.test=123)
    return <Comp Component={Component} pageProps={pageProps} {...rest}></Comp>
  }
  TestHocComo.getInitialProps = Comp.getInitialProps
  return TestHocComo
}

这样的好处是把redux内部的代码减少,减少了代码的耦合性,要用的时候只需要包裹一下组件,不必把组件内部代码写的很乱

redux集成到next

image-20200305132250479

renderer

可以使一个组件拥有另一个组件的样式

layout.jsx

const Comp = ({ color, children, style }) => (
  <div style={{ color, ...style }}>{children}</div>
);
<Contaniner renderer={<div className="header-inner" />}>
          <div className="header-left">
            <div className="logo">
              {/* <Icon type="github" /> */}
              <GithubOutlined style={IconStyle} />
            </div>
            <div>
              <Input.Search
                placeholder="搜索仓库"
                value={search}
                onChange={handleSearchChange}
                onSearch={handleSearch}
              />
            </div>
          </div>
          <div className="header-right">
            <div className="user">
              <Avatar size={40} icon={<UserOutlined />} />
            </div>
          </div>
</Contaniner>
<Content>
        <Contaniner
          //  comp="p" //传入默认标签是p
          renderer={<Comp color="red" />}
        >
          {children}
        </Contaniner>
</Content>
      

Container.jsx

import {cloneElement} from 'react'
import { render } from 'react-dom'
const style = {
  width:'100%',
  maxWidth:800,
  marginLeft:'auto',
  marginRight:'auto',
  paddingLeft:20,
  paddingRight:20
}
export default ({
  children,
  // comp:Comp='div', //接收默认组件,解构赋值把形参改为大写
  renderer
})=>{
  return cloneElement(renderer,{
    style:Object.assign({},renderer.props.style,style),
    children
  })
  // return <Comp style={style}>{children}</Comp>
}

image-20200311133025620

image-20200311133904360是renderer.props.style