├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── components ├── comp.jsx ├── layout.jsx └── lazy.jsx ├── lib ├── my-context.js └── with-redux.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── a.js ├── api │ └── hello.js ├── index.js └── test │ └── b.js ├── public ├── favicon.ico └── vercel.svg ├── server.js ├── store └── store.js ├── styles ├── Home.module.css └── globals.css └── test └── test-redis.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], // next babel default config 3 | "plugins": [ 4 | [ 5 | "import", 6 | { 7 | "libraryName": "antd" 8 | } 9 | ], 10 | [ 11 | "styled-components", { "ssr": true } 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ChongQingNoSubway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a nextjs-koa-radis project that is to learn different frameworks and SSR 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | npm install 8 | 9 | 10 | ```bash 11 | npm install 12 | npm run dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | ## Configuration and construction process 18 | #### we will need to make sure that node was installed on your computer 19 | 20 | #### The project main use frameworks : 21 | Front-end: react-nextjs 22 | Back-end: node - koa 23 | Database: redis 24 | 25 | - First we input 26 | ``` 27 | npm install -g create-next-app 28 | ``` 29 | 30 | 31 | for Introducing next framework 32 | 33 | - Second the nextjs is a server, and it can onley deal with SSR. So the node(koa) can be used to handle data interface and connection of database, keep session state. 34 | ``` 35 | npm install koa 36 | ``` 37 | 38 | and then create the file server.js in the project root directory for server configuration. 39 | 40 | - Third using redis as database and connect node to radis 41 | The radis(windows) installation : [microsoft-readis](https://github.com/microsoftarchive/redis/releases) version: 3.0.504 42 | After installation, we can set the port and password we want in the window.conf file 43 | open windows powershell in your radis installation directory 44 | 45 | ``` 46 | ./redis-server.exe ./redis.windows.conf 47 | ``` 48 | 49 | 50 | 51 | open a new windows powershell 52 | 53 | ``` 54 | ./redis-cli -p xxxx(setting port) 55 | auth xxxxxxx(password) 56 | ``` 57 | For test 58 | 59 | Use ioredis to connect node to radis 60 | ``` 61 | npm install ioredis 62 | ``` 63 | 64 | There are some test codes for node operation redis in the test directory 65 | 66 | - Fourth Integrated antd-design and load css 67 | 68 | First we load antd 69 | ``` 70 | npm install antd 71 | ``` 72 | 73 | Due to optimization issues, we use on-demand loading for antd components 74 | ``` 75 | npm install babel-plugin-import 76 | ``` 77 | 78 | Create a babelrc file in the root directory to configure the plug-in and babel 79 | ``` 80 | { 81 | "presets": ["next/babel"], // next babel default config (must add) 82 | "plugins": [ 83 | [ 84 | "import", 85 | { 86 | "libraryName": "antd" //import {button} from 'antd' = import button from 'antd/lib/button' 87 | //"style": "css" bug for here 88 | } 89 | ] 90 | ] 91 | } 92 | ``` 93 | 94 | 95 | 96 | Due to the problem of webpack, we add style to babelrc to cause an error. 97 | First we need @zeit/next-css to load css 98 | ``` 99 | npm install @zeit/next-css 100 | ``` 101 | and then add file next.config.js to root directory, writting to configuration of next-css loader 102 | 103 | 104 | after that ,we introduce globally in _app.js in the page directory: 105 | ``` 106 | import 'antd/dist/antd.css' 107 | ``` 108 | 109 | - Fifth add styled-component 110 | the jsx is not only way we write the Css, so I also installed styled-component that can write Css 111 | 112 | ``` 113 | npm install style-components babel-plugin-styled-components 114 | 115 | ``` 116 | and then I need to add plugin into file .babelrc under root directory 117 | 118 | ``` 119 | [ 120 | "styled-components", { "ssr": true } 121 | ] 122 | ``` 123 | Finally we need to confg the file _document.js under page diretory 124 | 125 | ``` 126 | import { ServerStyleSheet } from 'styled-components' 127 | 128 | static async getInitialProps(ctx) { 129 | const sheet = new ServerStyleSheet() 130 | const originalRenderPage = ctx.renderPage 131 | 132 | try { 133 | ctx.renderPage = () => originalRenderPage({ 134 | enhanceApp : App => props => sheet.collectStyles(), 135 | }) 136 | const props = await Document.getInitialProps(ctx) 137 | 138 | return { 139 | ...props, 140 | styles: <>{props.styles}{sheet.getStyleElement()} 141 | } 142 | 143 | }finally{ 144 | sheet.seal() 145 | } 146 | } 147 | ``` 148 | it is a part of file _document.js. 149 | 150 | 151 | - Sixth Load modules asynchronously && nextjs configuration 152 | if we need to load modules asynchronously to avoid waste of resource, we can use 153 | ``` 154 | 155 | import dynamic from 'next/dynamic' 156 | 157 | const Lazy = dynamic(import('../components/lazy')) 158 | 159 | 160 | ``` 161 | In the file in the /page directory 162 | 163 | 164 | 165 | According to our needs, we can modification Nextjs configuration at file next.config.js 166 | the Following shows are all optional configuration 167 | 168 | ``` 169 | //Output directory of compiled files 170 | distDir: 'dest', 171 | //whether to generate Etags for each routers(let the browser use the cache in the same request) 172 | generateEtags: true, 173 | //page content cache configuration 174 | onDemandEntries: { 175 | //how long the content is cached in memory 176 | maxInactiveAge: 25* 1000, 177 | //how many pages to cache 178 | pagesBufferLength: 2 179 | }, 180 | //define the type of pages files available in the page directory 181 | pageExtensions:['jsx','js'], 182 | // configuration bulidId 183 | generateBuildId: async () => { 184 | if(process.env.YOUR_BUILD_ID){ 185 | return process.env.YOUR_BUILD_ID 186 | } 187 | 188 | return null 189 | }, 190 | //manual modification webpack config 191 | webpack(config, options) { 192 | return config 193 | }, 194 | // modification webpackdevMiddleware configuration 195 | webpackDevMiddleware: config => { 196 | return config 197 | }, 198 | // config process.env 199 | env: { 200 | customKey: 'value', 201 | }, 202 | //the following two need to be read in 'next/config' 203 | // The configuration is only obtained when the server is rendering 204 | serverRuntimeConfig:{ 205 | mySecret: 'secret', 206 | secondSecret: process.env.SECOND_SECRET, 207 | }, 208 | // configuration available for both server-side rendering and client-server rendering 209 | publicRuntimeConfig: { 210 | staticFolder: '/static', 211 | }, 212 | 213 | ``` 214 | Like publicRuntimeConfig, env and serverRuntimeConfig , we can add them into module.exports = withCss({ }) 215 | , then get them in file in the /pages directory. (serverRuntimeConfig and publicRuntimeConfig need to import 'next/config') 216 | 217 | 218 | - Seventh introduce redux 219 | ``` 220 | npm install redux 221 | ``` 222 | and then we add the /page folder under root directory, Creating files in /page directory for State management. 223 | 224 | sometime we need to dispatch async action for update state, but the machanism of dispatch can not wait until get asynchronous data before execution. so we need to use redux-thunk 225 | 226 | ``` 227 | npm install redux-thunk 228 | ``` 229 | and then add the config to parameter applyMiddleware of method CreateStore 230 | 231 | ``` 232 | 233 | import applyMiddleware from 'redux' 234 | 235 | const store = createStore( 236 | allReducers,{ 237 | counter: initialState, 238 | user: userInitialState 239 | }, 240 | applyMiddleware(ReduxThunk) 241 | ) 242 | ``` 243 | 244 | we need to connect redux and component and install the redux-tool to moniter redux-State 245 | ``` 246 | npm install react-redux 247 | npm install redux-devtools-extension 248 | 249 | Example For react-Redux Config: 250 | import {connect} from 'react-redux' 251 | export default connect(function mapStateToProps)(function mapDispatchToProps)(index) 252 | 253 | redux-devtools-extension config(composeWithDevTools): 254 | const store = createStore( 255 | allReducers,{ 256 | counter: initialState, 257 | user: userInitialState 258 | }, 259 | composeWithDevTools(applyMiddleware(ReduxThunk)) 260 | ) 261 | 262 | ``` 263 | 264 | ## 265 | 266 | ======= 267 | 268 | -------------------------------------------------------------------------------- /components/comp.jsx: -------------------------------------------------------------------------------- 1 | import { Children } from "react"; 2 | 3 | export default ({ children }) => -------------------------------------------------------------------------------- /components/layout.jsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Button } from 'antd' 3 | 4 | export default ({ children }) => ( 5 | <> 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | {children} 16 | 17 | ) -------------------------------------------------------------------------------- /components/lazy.jsx: -------------------------------------------------------------------------------- 1 | export default ({ children }) => Lazy Component -------------------------------------------------------------------------------- /lib/my-context.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default React.createContext('') -------------------------------------------------------------------------------- /lib/with-redux.js: -------------------------------------------------------------------------------- 1 | import createStore from '../store/store' 2 | import React from 'react' 3 | const isServer = typeof window === 'undefined' 4 | const __NEXT_REUDX_STORE__ = '_NEXT_REDUX_STORE_' 5 | 6 | function getOrCreateStore(initialiStore) { 7 | if(isServer){ 8 | return createStore(initialiStore) 9 | } 10 | if(!window[__NEXT_REUDX_STORE__]) { 11 | window[__NEXT_REUDX_STORE__] = createStore(initialiStore) 12 | } 13 | 14 | return window[__NEXT_REUDX_STORE__] 15 | } 16 | 17 | export default Comp => { 18 | class WithReduxApp extends React.Component{ 19 | constructor(props) { 20 | super(props) 21 | this.reduxStore = getOrCreateStore(props.initializeStore) 22 | } 23 | 24 | render() { 25 | const {Component, pageProps,...rest} = this.props 26 | 27 | console.log(Component,pageProps) 28 | if(pageProps){ 29 | pageProps.test = '123' 30 | } 31 | return 32 | } 33 | 34 | } 35 | 36 | WithReduxApp.getInitialProps = async (ctx) => { 37 | const reduxStore = getOrCreateStore() 38 | ctx.reduxStore = reduxStore 39 | 40 | let appProps = {} 41 | if(typeof Comp.getInitialProps === 'function'){ 42 | appProps = await Comp.getInitialProps(ctx) 43 | } 44 | 45 | return { 46 | ...appProps, 47 | initializeStore: reduxStore.getState() 48 | } 49 | } 50 | return WithReduxApp 51 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withCss = require('@zeit/next-css') 2 | 3 | const configs = { 4 | //Output directory of compiled files 5 | distDir: 'dest', 6 | //whether to generate Etags for each routers(let the browser use the cache in the same request) 7 | generateEtags: true, 8 | //page content cache configuration 9 | onDemandEntries: { 10 | //how long the content is cached in memory 11 | maxInactiveAge: 25* 1000, 12 | //how many pages to cache 13 | pagesBufferLength: 2 14 | }, 15 | //define the type of pages files available in the page directory 16 | pageExtensions:['jsx','js'], 17 | // configuration bulidId 18 | generateBuildId: async () => { 19 | if(process.env.YOUR_BUILD_ID){ 20 | return process.env.YOUR_BUILD_ID 21 | } 22 | 23 | return null 24 | }, 25 | //manual modification webpack config 26 | webpack(config, options) { 27 | return config 28 | }, 29 | // modification webpackdevMiddleware configuration 30 | webpackDevMiddleware: config => { 31 | return config 32 | }, 33 | // config process.env 34 | env: { 35 | customKey: 'value', 36 | }, 37 | //the following two need to be read in 'next/config' 38 | // The configuration is only obtained when the server is rendering 39 | serverRuntimeConfig:{ 40 | mySecret: 'secret', 41 | secondSecret: process.env.SECOND_SECRET, 42 | }, 43 | // configuration available for both server-side rendering and client-server rendering 44 | publicRuntimeConfig: { 45 | staticFolder: '/static', 46 | }, 47 | 48 | } 49 | 50 | 51 | if(typeof require !== 'undefined'){ 52 | require.extensions['.css'] = file => {} 53 | } 54 | 55 | module.exports = withCss({ 56 | // env: { 57 | // customKey: 'value', 58 | // }, 59 | // serverRuntimeConfig:{ 60 | // mySecret: 'secret', 61 | // secondSecret: process.env.SECOND_SECRET, 62 | // }, 63 | // // configuration available for both server-side rendering and client-side rendering 64 | // publicRuntimeConfig: { 65 | // staticFolder: '/static', 66 | // }, 67 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-koa-project", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node server.js", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@zeit/next-css": "^1.0.1", 12 | "antd": "^4.9.4", 13 | "babel-plugin-import": "^1.13.3", 14 | "babel-plugin-styled-components": "^1.12.0", 15 | "ioredis": "^4.19.4", 16 | "koa": "^2.13.0", 17 | "koa-router": "^10.0.0", 18 | "moment": "^2.29.1", 19 | "next": "10.0.3", 20 | "react": "17.0.1", 21 | "react-dom": "17.0.1", 22 | "react-redux": "^7.2.2", 23 | "redux": "^4.0.5", 24 | "redux-devtools-extension": "^2.13.8", 25 | "redux-thunk": "^2.3.0", 26 | "styled-components": "^5.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import App, { Container} from 'next/app' 2 | import 'antd/dist/antd.css' 3 | import Layout from '../components/layout' 4 | import Mycontext from '../lib/my-context' 5 | import { Button } from 'antd' 6 | import { Provider } from 'react-redux' 7 | // why we need _app.js 8 | // 1. Use _app.js to fix layout 9 | // 2. keep some public state 10 | // 3. give other pages some customized data 11 | // 4. customize process of err 12 | import testHoc from '../lib/with-redux' 13 | 14 | class MyApp extends App { 15 | state={ 16 | context: 'contenxt' 17 | } 18 | static async getInitialProps(ctx) { 19 | // this method will be called at switch page 20 | console.log('app init') 21 | const { Component} = ctx 22 | let pageProps 23 | if(Component.getInitialProps){ 24 | pageProps = await Component.getInitialProps(ctx) 25 | } 26 | return { 27 | pageProps 28 | } 29 | } 30 | 31 | render() { 32 | const { Component, pageProps,reduxStore} = this.props 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | } 48 | 49 | export default testHoc(MyApp) 50 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, {Html, Head, Main, NextScript} from 'next/document' 2 | import { ServerStyleSheet } from 'styled-components' 3 | 4 | class MyDocument extends Document { 5 | 6 | static async getInitialProps(ctx) { 7 | const sheet = new ServerStyleSheet() 8 | const originalRenderPage = ctx.renderPage 9 | 10 | try { 11 | ctx.renderPage = () => originalRenderPage({ 12 | enhanceApp : App => props => sheet.collectStyles(), 13 | }) 14 | const props = await Document.getInitialProps(ctx) 15 | 16 | return { 17 | ...props, 18 | styles: <>{props.styles}{sheet.getStyleElement()} 19 | } 20 | 21 | }finally{ 22 | sheet.seal() 23 | } 24 | } 25 | 26 | render() { 27 | return 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | } 37 | } 38 | 39 | 40 | export default MyDocument -------------------------------------------------------------------------------- /pages/a.js: -------------------------------------------------------------------------------- 1 | import Comp from '../components/comp' 2 | import Link from 'next/link' 3 | import { withRouter } from 'next/router' 4 | import styled from 'styled-components' 5 | import dynamic from 'next/dynamic' 6 | // import getConfig from 'next/config' 7 | // import moment from 'moment' 8 | // export default () => ( 9 | // 10 | //
a
11 | // 12 | // ) 13 | 14 | 15 | // import lazy from '../components/layout' 16 | 17 | const Lazy = dynamic(import('../components/lazy')) 18 | 19 | // const {serverRuntimeConfig, publicRuntimeConfig} = getConfig() 20 | 21 | // style-component css 22 | const Title = styled.h1` 23 | color: yellow; 24 | font-size: 40px 25 | ` 26 | 27 | const A = ({ router, name, time }) => { 28 | 29 | 30 | // console.log(serverRuntimeConfig,publicRuntimeConfig) 31 | return( 32 | <> 33 | {time} 34 | {/* hash route */} 35 | 36 |
a{router.query.id}{name}
37 | 38 |
{process.env.customKey}
39 | 40 | 41 | 46 | 49 | 50 | ) 51 | } 52 | 53 | A.getInitialProps = async () => { 54 | // Asynchronous loading 55 | // Avoid some page do need to moment also loading it 56 | // if we use import moment every js , it make the loading of modules is huge. 57 | // Avoid module resource waste 58 | const moment = await import('moment') 59 | 60 | 61 | const promise = new Promise((resolve) => { 62 | setTimeout(() => { 63 | resolve({ 64 | name: 'jacky', 65 | // if we want to use moment 66 | // must add default 67 | time: moment.default(Date.now()-60*1000).fromNow(), 68 | }) 69 | },1000) 70 | }) 71 | return await promise 72 | } 73 | 74 | export default withRouter(A) -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default (req, res) => { 4 | res.statusCode = 200 5 | res.json({ name: 'John Doe' }) 6 | } 7 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd' 2 | import Link from 'next/link' 3 | import Router from 'next/router' 4 | 5 | import {connect} from 'react-redux' 6 | import { add } from '../store/store' 7 | 8 | const events = [ 9 | 'routechangeStart', 10 | 'routeChangeComplete', 11 | 'routeChangeError', 12 | 'beforeHistoryChange', 13 | 'hashChangeStart', 14 | 'hashChangeComplete' 15 | ] 16 | 17 | function makeEvent(type) { 18 | return (...args) => { 19 | console.log(type, ...args) 20 | } 21 | } 22 | 23 | events.forEach(event => { 24 | Router.events.on(event, makeEvent(event)) 25 | }) 26 | 27 | const index = ({counter, username,rename,add}) => { 28 | 29 | // function gotoTestB(){ 30 | // Router.push({ 31 | // pathname: '/a', 32 | // query: { 33 | // id: 98 34 | // } 35 | // },'/a/98') 36 | // } 37 | 38 | return ( 39 | <> 40 | {/* 41 | 42 | 43 | 44 | */} 45 | 46 | 47 | Count: {counter} 48 | 49 | Username: {username} 50 | rename(e.target.value)}/> 51 | 52 | 53 | ) 54 | 55 | } 56 | 57 | index.getInitialProps = async ({reduxStore}) => { 58 | reduxStore.dispatch(add(3)) 59 | return {} 60 | } 61 | 62 | export default connect(function mapStateToProps(state) { 63 | return { 64 | counter: state.counter.count, 65 | username: state.user.name 66 | } 67 | }, function mapDispatchToProps(dispatch){ 68 | return { 69 | add: (num) => dispatch({ 70 | type: 'ADD', 71 | num 72 | }), 73 | rename: (name) => dispatch({ 74 | type: 'Update_USERNAME', 75 | name 76 | }) 77 | } 78 | })(index) -------------------------------------------------------------------------------- /pages/test/b.js: -------------------------------------------------------------------------------- 1 | import React, {useState,useReducer, useContext,useEffect,useLayoutEffect,useRef,memo,useMemo,useCallback} from 'react' 2 | 3 | import Mycontext from '../../lib/my-context' 4 | 5 | 6 | class MyCount extends React.Component{ 7 | //set ref before hook come up 8 | constructor() { 9 | super() 10 | this.ref = React.createRef() 11 | } 12 | 13 | state = { 14 | count: 0 15 | } 16 | 17 | componentDidMount() { 18 | // get ref p 19 | this.ref.current 20 | this.interval = setInterval(() => { 21 | this.setState({count: this.state.count + 1}) 22 | },1000) 23 | } 24 | 25 | componentWillUnMount() { 26 | if(this.interval) { 27 | clearInterval(this.interval) 28 | } 29 | } 30 | 31 | render() { 32 | return {this.state.count} 33 | } 34 | } 35 | 36 | 37 | function countReducer(state,action) { 38 | switch (action.type) { 39 | case 'add': 40 | return state + 1 41 | case 'minus': 42 | return state-1 43 | default: 44 | return state 45 | } 46 | } 47 | 48 | 49 | 50 | 51 | function MycountFunc() { 52 | // const [count,setCount] = useState(0) //[a,b] 53 | 54 | //when component update, it will execute this callback 55 | const [count, dispatchCount] = useReducer(countReducer, 0) 56 | // setCount(1) 57 | // setCount((c) => { 58 | // c + 1 59 | // }) 60 | 61 | const [name, setName] = useState('jocky') 62 | 63 | const context = useContext(Mycontext) 64 | 65 | const inputRef = useRef() 66 | 67 | 68 | // optimize hooks render 69 | const config = useMemo(() => ({ 70 | text: `count is ${count}`, 71 | color: count>3? 'red':'blue', 72 | }),[count]) 73 | 74 | //called at update component 75 | useEffect(()=> { 76 | // const interval = setInterval(()=> { 77 | // // setCount( c => c + 1) 78 | // dispatchCount({type:'minus'}) 79 | // },1000) 80 | // // Unmount 81 | // return () => clearInterval(interval) 82 | console.log(inputRef) 83 | console.log('effect invoked') 84 | return () => console.log('effect deteched') 85 | },[count,name]) 86 | 87 | 88 | // before dom tree is rendering(before monting to dom) 89 | useLayoutEffect(() => { 90 | console.log('effectlayout invoked') 91 | return () => console.log('effectlayout deteched') 92 | },[count,name]) 93 | 94 | // optimize hooks render 95 | // memo the callbackfunction usememo or useCallback 96 | 97 | //const handleButton = useCallback(() => dispatchCount({type:'add'}),[]) 98 | 99 | const handleButton = useMemo( () => () => dispatchCount({type:'add'}),[],) 100 | 101 | // closure test 102 | const handleclick1 = function () { 103 | setTimeout(()=> { 104 | alert(count) 105 | },2000) 106 | } 107 | 108 | return ( 109 |
110 | setName(e.target.value)}/> 111 | 112 |

{context}

113 | 114 |
115 | ) 116 | } 117 | 118 | 119 | // must be mome the component that u do wanna to render when there is no any changes 120 | const Child =memo(function Child({onButtonClick, config}) { 121 | console.log('child render') 122 | return( 123 | 126 | ) 127 | }) 128 | 129 | export default MycountFunc 130 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChongQingNoSubway/nextjs-koa-redis-fullstackCil/07c6f7ffc4f43d9147040238fd95476682aa2c46/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const next = require('next') 3 | const Router = require('koa-router') 4 | 5 | const dev = process.env.NODE_ENV!== 'production' 6 | const app = next({ dev }) 7 | const handle = app.getRequestHandler() 8 | 9 | 10 | // wait to pages to complie 11 | app.prepare().then(() => { 12 | const server = new Koa() 13 | const router = new Router() 14 | 15 | //TEST 16 | // router.get('/test',(ctx)=> { 17 | // // ctx.body = `

requset test ${ctx.params.id}

` 18 | // ctx.body = { success : true} 19 | // ctx.set('Content-Type', 'application/json') 20 | // }) 21 | 22 | 23 | router.get('/a/:id',async (ctx)=> { 24 | const id = ctx.params.id 25 | await handle(ctx.req, ctx.res, { 26 | pathname: '/a', 27 | query: { id } 28 | }) 29 | ctx.respond = false 30 | }) 31 | 32 | server.use(router.routes()) 33 | 34 | server.use(async (ctx, next) =>{ 35 | await handle(ctx.req , ctx.res) 36 | ctx.respond = false 37 | }) 38 | 39 | // server.use(async(ctx, next)=>{ 40 | // const path = ctx.path 41 | // const method = ctx.method 42 | // ctx.body =`koa ${path} ${method}` 43 | // await next() 44 | // }) 45 | 46 | // server.use(router.routes()) 47 | 48 | server.listen(3000,()=>{ 49 | console.log('koa server is starting') 50 | }) 51 | }) 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /store/store.js: -------------------------------------------------------------------------------- 1 | import {createStore, combineReducers, applyMiddleware} from 'redux' 2 | import ReduxThunk from 'redux-thunk' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | const initialState = { 5 | count: 0 6 | } 7 | 8 | const userInitialState = { 9 | name: 'jock', 10 | age : 15 11 | } 12 | 13 | const ADD = 'ADD' 14 | 15 | function countReducer(state = initialState, action){ 16 | console.log(state,action.type) 17 | switch (action.type) { 18 | case ADD: 19 | return { count: state.count + (action.num || 1) } 20 | default : 21 | return state 22 | } 23 | } 24 | 25 | const Update_USERNAME = 'Update_USERNAME' 26 | 27 | function userReducer(state = userInitialState, action){ 28 | switch(action.type){ 29 | case Update_USERNAME: 30 | return { 31 | ...state, 32 | name: action.name, 33 | } 34 | default: 35 | return state 36 | } 37 | } 38 | 39 | 40 | const allReducers = combineReducers({ 41 | counter:countReducer, 42 | user:userReducer 43 | }) 44 | 45 | const store = createStore( 46 | allReducers,{ 47 | counter: initialState, 48 | user: userInitialState 49 | }, 50 | composeWithDevTools(applyMiddleware(ReduxThunk)) 51 | ) 52 | 53 | //action creator 54 | export function add(num) { 55 | return { 56 | type: ADD, 57 | num, 58 | } 59 | } 60 | 61 | //async creator 62 | function addAsync(num) { 63 | return (dispatch) => { 64 | setTimeout(() => { 65 | dispatch(add(num)) 66 | }, 1000); 67 | } 68 | } 69 | 70 | store.dispatch(add(3)) 71 | 72 | console.log(store.getState()) 73 | store.dispatch({type:ADD}) 74 | store.dispatch({ type:Update_USERNAME , name: "wenwenwen" }) 75 | console.log(store.getState()) 76 | 77 | // listen changes of store 78 | store.subscribe(() => { 79 | console.log('change',store.getState()) 80 | }) 81 | 82 | store.dispatch(addAsync(4)) 83 | store.dispatch({ type:Update_USERNAME , name: "ssss" }) 84 | 85 | 86 | 87 | 88 | export default function initializeStore(state) { 89 | const store = createStore( 90 | allReducers, 91 | Object.assign({}, { 92 | counter: initialState, 93 | user: userInitialState 94 | },state), 95 | composeWithDevTools(applyMiddleware(ReduxThunk)) 96 | ) 97 | 98 | return store 99 | } -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .main { 11 | padding: 5rem 0; 12 | flex: 1; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | .footer { 20 | width: 100%; 21 | height: 100px; 22 | border-top: 1px solid #eaeaea; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | } 27 | 28 | .footer img { 29 | margin-left: 0.5rem; 30 | } 31 | 32 | .footer a { 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | } 37 | 38 | .title a { 39 | color: #0070f3; 40 | text-decoration: none; 41 | } 42 | 43 | .title a:hover, 44 | .title a:focus, 45 | .title a:active { 46 | text-decoration: underline; 47 | } 48 | 49 | .title { 50 | margin: 0; 51 | line-height: 1.15; 52 | font-size: 4rem; 53 | } 54 | 55 | .title, 56 | .description { 57 | text-align: center; 58 | } 59 | 60 | .description { 61 | line-height: 1.5; 62 | font-size: 1.5rem; 63 | } 64 | 65 | .code { 66 | background: #fafafa; 67 | border-radius: 5px; 68 | padding: 0.75rem; 69 | font-size: 1.1rem; 70 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 71 | Bitstream Vera Sans Mono, Courier New, monospace; 72 | } 73 | 74 | .grid { 75 | display: flex; 76 | align-items: center; 77 | justify-content: center; 78 | flex-wrap: wrap; 79 | max-width: 800px; 80 | margin-top: 3rem; 81 | } 82 | 83 | .card { 84 | margin: 1rem; 85 | flex-basis: 45%; 86 | padding: 1.5rem; 87 | text-align: left; 88 | color: inherit; 89 | text-decoration: none; 90 | border: 1px solid #eaeaea; 91 | border-radius: 10px; 92 | transition: color 0.15s ease, border-color 0.15s ease; 93 | } 94 | 95 | .card:hover, 96 | .card:focus, 97 | .card:active { 98 | color: #0070f3; 99 | border-color: #0070f3; 100 | } 101 | 102 | .card h3 { 103 | margin: 0 0 1rem 0; 104 | font-size: 1.5rem; 105 | } 106 | 107 | .card p { 108 | margin: 0; 109 | font-size: 1.25rem; 110 | line-height: 1.5; 111 | } 112 | 113 | .logo { 114 | height: 1em; 115 | } 116 | 117 | @media (max-width: 600px) { 118 | .grid { 119 | width: 100%; 120 | flex-direction: column; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /test/test-redis.js: -------------------------------------------------------------------------------- 1 | //test connect redis 2 | 3 | async function test() { 4 | const Redis = require('ioredis') 5 | 6 | 7 | const redis = new Redis({ 8 | port: 6378, 9 | host:'127.0.0.1', //redis port 10 | // family: 4, // IPv4 or IPv6 11 | password: 123456 12 | }) 13 | // set(key,value) 14 | // setex(key,Expiration-time(second), value) 15 | await redis.setex('c',10,123) 16 | const keys = await redis.keys('*') 17 | console.log(keys) 18 | } 19 | 20 | test() --------------------------------------------------------------------------------