redux、scss/less、antd按需加载、typescript整合nextjs

redux、scss/less、antd按需加载、typescript整合nextjs步骤如下:(如有侵权,可以留言,?)

本次案例使用了nextjs的脚手架搭建和一些依赖安装

*在本博客后面会贴上代码案例,不想看步骤的大佬可以直接拉到最下面,萌新博客见谅

1.安装create-next-app(mac用户记得加上sudo)

cnpm i -g create-next-app //全局安装next脚手架,使用npx的用户可以跳过这步骤

2.创建脚手架(脚手架默认引入antd)

create-next-app --example with-ant-design with-redux-app // 引入ant-design的next项目
//npx安装
npx create-next-app --example with-ant-design with-redux-app 

3.安装scss(不需要使用的可以跳过这个步骤)

null-loader 一个加载器,返回一个空模块(萌新不是很清楚)

cnpm i @zeit/next-sass node-sass null-loader --save  //@zeit/next-sass依赖于@zeit/next-css 所以无需引入css

4.安装next-compose-plugins(让项目同时使用sass/less/css) 想使用less的大佬看下面的less配置

cnpm i next-compose-plugins --save //结合sass 、 less 和 css

5.重新配置next.config.js文件

const withCss = require('@zeit/next-css')
const withSass = require('@zeit/next-sass');
const withPlugins = require("next-compose-plugins/lib");//结合sass css
module.exports = withPlugins([withSass,withCss],{//[withSass,withCss]可以引入各种依赖,less也可以
  webpack: (config, { isServer }) => {
    if (isServer) {
      const antStyles = /antd\/.*?\/style\/css.*?/
      const origExternals = [...config.externals]
      config.externals = [
        (context, request, callback) => {
          if (request.match(antStyles)) return callback()
          if (typeof origExternals[0] === 'function') {
            origExternals[0](context, request, callback)
          } else {
            callback()
          }
        },
        ...(typeof origExternals[0] === 'function' ? [] : origExternals),
      ]
      config.module.rules.unshift({
        test: antStyles,
        use: 'null-loader',
      });
    }
    return config
  },
})

6.引入redux、react-redux、redux-thunk、redux-devtools-extension

cnpm i react-redux redux redux-devtools-extension redux-thunk --save//引入依赖

7.引入下面的高阶组件with-redux-redux.js(代码如下)

import React from 'react'
import { initializeStore } from '../redux/store'//引入初始化的store

const isServer = typeof window === 'undefined';//判断一下window在不在
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'//定一个常量用来对象存储的key名

function getOrCreateStore (initialState) {
  // Always make a new store if server, otherwise state is shared between requests
  if (isServer) {//当window不存在的时候 直接返回初始化的整个store 就是在服务器的时候
    return initializeStore(initialState)
  }

  // Create store if unavailable on the client and set it on the window object
  if (!window[__NEXT_REDUX_STORE__]) {//这个索引的对象不存在 客户端的时候,注入一个属性
    window[__NEXT_REDUX_STORE__] = initializeStore(initialState)//就初始化整个store给它
  }
  return window[__NEXT_REDUX_STORE__]//最终返回这个对象的key为__NEXT_REDUX_STORE__的值,也就是初始化的整个store
}

export default App => {//App为传入的组件
  return class AppWithRedux extends React.Component {
    static async getInitialProps (appContext) {
      // Get or Create the store with `undefined` as initialState
      // This allows you to set a custom default initialState
      const reduxStore = getOrCreateStore();//获取初始化的整个store

      // Provide the store to getInitialProps of pages
      appContext.ctx.reduxStore = reduxStore//然后赋值给整个上下文

      let appProps = {}
      if (typeof App.getInitialProps === 'function') {
        appProps = await App.getInitialProps(appContext)
        // console.log(appProps);
      }

      return {
        ...appProps,
        initialReduxState: reduxStore.getState()//获取初始化的state值
      }
    }

    constructor (props) {
      super(props)
      // console.log(props);
      this.reduxStore = getOrCreateStore(props.initialReduxState);//获取整个store
    }

    render () {
      return <App {...this.props} reduxStore={this.reduxStore} />
    }
  }
}

8.或者使用create-next-app --example with-redux创建一个内置redux的脚手架,然后去lib目录下面拿到该文件

9.在pages目录下面创建_app.jsx文件用来注入高阶组件(代码如下)

import App, { Container } from 'next/app'
import React from 'react'
import withReduxStore from '../lib/with-redux-store'//引入高阶组件
import { provider } from 'react-redux';//引入并使用
class MyApp extends App {
  render () {
    const { Component, pageProps, reduxStore } = this.props
    //reduxStore:就是创建的redux的store
    return (
      <Container>
	     <Provider store={reduxStore}> 
	       <Component {...pageProps} />
	     </Provider>
      </Container>
    )
  }
}

export default withReduxStore(MyApp);

同时在redux目录下面还有下面这个store.js文件(小案例)

import { createStore,combineReducers,applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
// import createSagaMiddleware from 'redux-saga';//选这个 或者上面的那个redux-thunk  这个需要安装yarn add next-redux-saga redux-saga --save
// initialState
const exampleInitialState = {
  todos:['hello next-redux']
}
// action-type
const actionTypes = {
  ADD_TODO:'ADD_TODO'
}
// REDUCERS(reducer)
const reducer = (state = exampleInitialState, action) => {
  switch (action.type) {
    case actionTypes.ADD_TODO:
      const todos = [...state.todos];
      todos.push(action.todo);
      return Object.assign({},state,{
        todos
      });
    default:
      return state
  }
}
// const reducers = combineReducers({
//   reducer
// });

// actions
export const addTodo = todo => ({type:actionTypes.ADD_TODO,todo});
export const asyncAddToDo = (todo) => dispatch => {
  return setTimeout(()=>dispatch(addTodo(todo)),2000);
}
// store 高阶组件调用写法,最好不要修改,修改的话就需要修改高阶组件的配置
export function initializeStore (initialState = exampleInitialState) {
  return createStore(
    reducer,
    initialState,
    applyMiddleware(thunk)
  )
}

10.引入装饰器写法 @babel/plugin-proposal-decorators

创建一个.babelrc文件在项目的根目录下

cnpm i @babel/plugin-proposal-decorators --save

.babelrc代码如下

{
  "presets": ["next/babel"],//next/babel next内置的模块,脚手架自带无需安装
  "plugins": [
    // @babel/plugin-proposal-decorators 这个插件,同时启用装饰器
    ["@babel/plugin-proposal-decorators", {
      "legacy": true
    }]
  ]
}

这样我们就能使用装饰器了,不过我们先配置好先

11.引入less,antd按需加载(antd创建项目的时候自带)

cnpm i @zeit/next-less less -S //引入less的依赖
cnpm i babel-plugin-import -S //配置babel按需加载

然后.babelrc文件的配置如下

{
  "presets": ["next/babel"],//next/babel next内置的模块,脚手架自带无需安装
  "plugins": [
    // @babel/plugin-proposal-decorators 这个插件,同时启用装饰器
    ["@babel/plugin-proposal-decorators", {
      "legacy": true
    }],
    [
      "import",
      {
        "libraryName": "antd",
        "style": "css"//这个改成ture,就可以使用less编译了,同时也能更改主题
      }
    ]
  ]
}

订制主题的配置暂时还没找到方式,不过,我们先配置一下TypeScript版本的nextjs

12.引入@zeit/next-typescript、TypeScript支持 (注意全局需要有TypeScript依赖)

cnpm i typescript -g //(本地没有的大佬先执行这条命令)
cnpm i @zeit/next-typescript  -D//引入next官方推荐的方式

.babelrc的最终配置如下

{
  "presets": ["next/babel","@zeit/next-typescript/babel"],//@zeit/next-typescript 引入ts, yarn add @zeit/next-typescript --save
  "plugins": [
    // 1.可以使用装饰器decorator @babel/plugin-proposal-decorators 这个插件
    ["@babel/plugin-proposal-decorators", {
      "legacy": true
    }],
    //yarn add @zeit/next-css @zeit/next-less less 
    // yarn add babel-plugin-import   这些安装之后可以配置less配置+按需加载
    // 按需加载并且可以使用less的配置
    [
      "import",
      {
        "libraryName": "antd",
        "style": "css"
      }
    ]
  ]
}

next.config.js的最终配置如下

const withCss = require('@zeit/next-css')
const withSass = require('@zeit/next-sass');
// const withLess = require('@zeit/next-less');//需要使用less不实用scss的大佬,把withSass替换成withSass即可,如果都需要就都引入
const withTypescript = require('@zeit/next-typescript');//引入typescript,让next解析
const withPlugins = require("next-compose-plugins/lib");//结合sass css

module.exports = withPlugins([withSass,withCss,withTypescript],{
  webpack: (config, { isServer }) => {
    if (isServer) {
      const antStyles = /antd\/.*?\/style\/css.*?/
      const origExternals = [...config.externals]
      config.externals = [
        (context, request, callback) => {
          if (request.match(antStyles)) return callback()
          if (typeof origExternals[0] === 'function') {
            origExternals[0](context, request, callback)
          } else {
            callback()
          }
        },
        ...(typeof origExternals[0] === 'function' ? [] : origExternals),
      ]
      config.module.rules.unshift({
        test: antStyles,
        use: 'null-loader',
      });
    }
    return config
  },
})

13.同时别忘记了tsconfig.json这个文件,配置如下,直接粘贴

{
    "compilerOptions": {
        "allowJs": true,
        "experimentalDecorators":true,
        "allowSyntheticDefaultImports": true,
        "jsx": "preserve",
        "lib": ["dom", "es2019"],
        "module": "esnext",
        "moduleResolution": "node",
        "noEmit": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "preserveConstEnums": true,
        "removeComments": false,
        "skipLibCheck": true,
        "sourceMap": true,
        "strict": false,
        "target": "esnext"
    }
}

14.为了防止不必要的错误,我们还需要引入@types/node

cnpm i @types/node --save

配置到这里就差不多完成了全部配置,有些许不足大佬们就提个意见什么?

*这样我们就能使用我们的TypeScript版本的next了
	如果不需要使用TypeScript的可以省略12以下的配置

最终配置的一些使用代码:

需要axios等的大佬可以自己完成配置,下面的package.json是本人的暂时全部配置(未引入less)

{
  "name": "with-ant-design",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@babel/plugin-proposal-decorators": "^7.4.4",
    "@types/node": "^12.7.1",
    "@zeit/next-css": "^1.0.1",
    "@zeit/next-sass": "^1.0.1",
    "@zeit/next-typescript": "^1.1.1",
    "antd": "^3.9.2",
    "babel-plugin-import": "^1.9.1",
    "next": "^8.1.0",
    "next-compose-plugins": "^2.2.0",
    "node-sass": "^4.12.0",
    "null-loader": "2.0.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.1.0",
    "redux": "^4.0.1",
    "redux-devtools-extension": "^2.13.8",
    "redux-thunk": "^2.3.0"
  },
  "license": "ISC"
}

1.component/todo.tsx

import React, { PureComponent } from 'react';
import { Button, Input, List } from 'antd';
import { connect } from 'react-redux'
import { addTodo,asyncAddToDo } from '../redux/store';
const Item = List.Item;

interface TodoProps{
    todos?:Array<string>; 
    addTodo?:(todo:string)=>void;
    asyncAddToDo?:(todo:string)=>void;
}
const mapStateToProps = state => {
    return ({ todos: state.todos });
};
const mapDispatchToProps = { addTodo,asyncAddToDo };
@connect(
    mapStateToProps,
    mapDispatchToProps
)
class Todo extends PureComponent<TodoProps,{todo:string}> {
    constructor(props:TodoProps) {
        super(props);
        this.state = {
            todo: ""
        }
    }
    changeTodo = el => {
        this.setState({
            todo: el.target.value
        });
    }
    //1.2这是给父组件调用的函数
    childMethod = () => {
        console.log(this.state.todo);
        return this.state.todo;
    }
    render() {
        const { todos, addTodo,asyncAddToDo } = this.props;
        const { todo } = this.state;
        const liList = todos.map((todo, index) => (<Item key={index}>{todo}</Item>));
        return (
            <div>
                <div>
                    <Input type="text" defaultValue={todo} onChange={this.changeTodo} />
                    <Button onClick={addTodo.bind(this,todo)}>添加</Button>
                    <Button onClick={asyncAddToDo.bind(this,todo)}>添加</Button>
                </div>
                <List>
                    {liList}
                </List>
            </div>
        );
    }
}
export default Todo;

2. pages/index.tsx

import '../assets/index.scss';
import Todo from '../components/todo';
function Index(): JSX.Element {
    return (
        <Todo />
    );
}
export default Index;

3.pages/_app.jsx

import App, { Container } from 'next/app';
import React from 'react';
import withReduxStore from '../lib/with-redux-store';//高阶组件
import { Provider } from 'react-redux';
@withReduxStore//直接使用装饰器的写法,注入高阶组件
class MyApp extends App {
  render () {
    const { Component, pageProps, reduxStore } = this.props;
    //reduxStore 通过高阶组件产生的store
    return (
      <Container>
        <Provider store={reduxStore}>
          <Component {...pageProps} />
        </Provider>
      </Container>
    )
  }
}

export default MyApp;

4.最终.babelrc文件配置

{
  "presets": ["next/babel"],//next/babel next内置的模块,脚手架自带无需安装
  "plugins": [
    // @babel/plugin-proposal-decorators 这个插件,同时启用装饰器
    ["@babel/plugin-proposal-decorators", {
      "legacy": true
    }],
    [
      "import",
      {
        "libraryName": "antd",
        "style": "css"//这个改成ture,就可以使用less编译了,同时也能更改主题
      }
    ]
  ]
}

5. 最终next.config.js文件配置

const withCss = require('@zeit/next-css')
const withSass = require('@zeit/next-sass');
// const withLess = require('@zeit/next-less');//需要使用less不实用scss的大佬,把withSass替换成withSass即可,如果都需要就都引入
const withTypescript = require('@zeit/next-typescript');//引入typescript,让next解析
const withPlugins = require("next-compose-plugins/lib");//结合sass css

module.exports = withPlugins([withSass,withCss,withTypescript],{
  webpack: (config, { isServer }) => {
    if (isServer) {
      const antStyles = /antd\/.*?\/style\/css.*?/
      const origExternals = [...config.externals]
      config.externals = [
        (context, request, callback) => {
          if (request.match(antStyles)) return callback()
          if (typeof origExternals[0] === 'function') {
            origExternals[0](context, request, callback)
          } else {
            callback()
          }
        },
        ...(typeof origExternals[0] === 'function' ? [] : origExternals),
      ]
      config.module.rules.unshift({
        test: antStyles,
        use: 'null-loader',
      });
    }
    return config
  },
})

最终tsconfig.json配置

{
    "compilerOptions": {
        "allowJs": true,
        "experimentalDecorators":true,
        "allowSyntheticDefaultImports": true,
        "jsx": "preserve",
        "lib": ["dom", "es2019"],
        "module": "esnext",
        "moduleResolution": "node",
        "noEmit": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "preserveConstEnums": true,
        "removeComments": false,
        "skipLibCheck": true,
        "sourceMap": true,
        "strict": false,
        "target": "esnext"
    }
}

然后开启服务,访问即可使用啦(使用Typescript只需要把.jsx后缀文件改动成.tsx后缀文件,ts和js能共存使用)

*最后,打一波小广告,如果需要使用useReducer整合的大佬可以看我的另外一个推文

连接如下,感觉还可以的大佬可以收藏一波,提意见什么的.

useRedcuer,使用createContext, useContext, useReducer整合多个reducer案例

代码获取:(感觉不错可以给个star哈 ?)

代码地址(github):点击这里跳转

你可能感兴趣的:(react,TypeScript,Nextjs)