前端:React+React-router+Redux+ant Design+TypeScript+Scss 后端:SpringBoot+Mybatis實現(xiàn)登錄的一次嘗試


\color{red}{寫在前面}

首先,我在日常項目中常用vue,畢竟相比于React,vue更容易上手,學(xué)習(xí)成本也比較低。但其實對React,我早就有心研究一番了,最近幾天閑著沒事就開始著手研究了一下,也算是剛剛?cè)腴T。此博客僅供各位想要入坑react和typeScript的同學(xué)參考,有什么不足之處請各位不吝指出,小弟十分感謝!下面話不多說進(jìn)入正題吧。。。

注:開始之前請先安裝node環(huán)境,至于使用yarn還是npm就看各位的心情了,本文使用npm,安裝依賴推薦使用淘寶鏡像(cnpm),安裝方法不會的同學(xué)請自行百度。。

一、創(chuàng)建React+TypeScript+ant Design項目

第一步
npm install -g create-react-app //全局安裝create-react-app
 
第二步
create-react-app antd-demo-ts --scripts-version=react-scripts-ts //創(chuàng)建一個使用react、ant Desgin及typescript的項目

創(chuàng)建項目完成圖示如下:


創(chuàng)建項目完成圖示.png

然后我們進(jìn)入項目并啟動:

cd antd-demo-ts //進(jìn)入項目

npm start //啟動應(yīng)用

此時瀏覽器會訪問 http://localhost:3000/ ,看到 Welcome to React 的界面就算成功了。

二、測試ant Desgin引用是否成功

cnpm install antd --save-dev  //使用cnpm安裝ant design依賴并寫入package.json文件中

安裝成功后,我們先修改 src/App.tsx 文件

import * as React from 'react';
import Button from 'antd/lib/button';
import './App.css';
 
import logo from './logo.svg';
 
class App extends React.Component {
  public render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.tsx</code> and save to reload.
        </p>
        <Button type="primary">AntDesgin按鈕</Button>
      </div>
    );
  }
}
 
export default App;

然后修改 src/App.css 引入 antd 的樣式:

@import '~antd/dist/antd.css'; //在App.css文件頂部引入
 
.App {
  text-align: center;
}
 
...

修改完成后 npm start 啟動項目查看效果。。

\color{red}{但是~~,此時你會發(fā)現(xiàn)編譯報錯了,錯誤信息如下圖:}
error.png

本菜當(dāng)時看到這個問題的時候也是一臉懵逼,什么情況呢?上網(wǎng)找答案之后發(fā)現(xiàn)原來是ts校驗規(guī)則的問題,這點我實在非常想要吐槽,import引入居然要按照字母排序,十分不適應(yīng),后續(xù)還會有更多的ts校驗規(guī)則的問題,在此就先解決再繼續(xù)往下進(jìn)行。

\color{red}{解決方法:}

修改項目根目錄下 tslint.json 文件

{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts"
    ]
  },
  "rules": {
      "ordered-imports": false,
      "prefer-const": false,
      "no-console":false,
      "no-debugger":false,
      "await-promise":false,
      "curly":false,
      "no-empty":false,
      "no-for-in-array":false,
      "no-invalid-this":false,
      "no-var-keyword":false,
      "member-access": false,
      "max-classes-per-file":false,
      "prefer-for-of":false,
      "triple-equals":false,
      "no-unused-expression": false,
      "prefer-readonly": false,
      "comment-format":false,
      "no-unnecessary-initializer": false,
      "no-unused-variable": false,
      "object-literal-sort-keys": false,
      "no-string-literal": false,
      "no-default-export":false
  }
}

將 tslint.json文件內(nèi)容替換為上述代碼即可解決問題,規(guī)則解釋如下(官方文檔 https://palantir.github.io/tslint/rules/ ,參考文檔 https://www.cnblogs.com/wyy5552/p/8796695.html):
以上解決了ts校驗規(guī)則的問題后,重新運行項目發(fā)現(xiàn)編譯成功,運行效果圖如下:

run.png

此時我們成功引用進(jìn)來了ant Desgin
三、實際項目中的ant design按需引入的性能優(yōu)化
\color{red}{(注:此步驟不是必須,可選擇性跳過)}

我們現(xiàn)在已經(jīng)把組件成功運行起來了,但是在實際開發(fā)過程中還有很多問題,例如上面的例子實際上加載了全部的 antd 組件的樣式(對前端性能是個隱患)。

此時我們需要對 create-react-app 的默認(rèn)配置進(jìn)行自定義,這里我們使用 react-app-rewired(一個對 create-react-app 進(jìn)行自定義配置的社區(qū)解決方案)。

引入 react-app-rewired 并修改 package.json 里的啟動配置:

cnpm install react-app-rewired --dev //cnpm 安裝 react-app-rewired
/* package.json */
"scripts": {
-   "start": "react-scripts-ts start", //原代碼
+   "start": "react-app-rewired start --scripts-version react-scripts-ts", //修改后
-   "build": "react-scripts-ts build", //原代碼
+   "build": "react-app-rewired build --scripts-version react-scripts-ts", //修改后
-   "test": "react-scripts-ts test --env=jsdom", //原代碼
+   "test": "react-app-rewired test --env=jsdom --scripts-version react-scripts-ts", //修改后
}

然后在項目根目錄創(chuàng)建一個 config-overrides.js 用于修改默認(rèn)配置:

module.exports = function override(config, env) {
  // 等會將要修改的代碼塊
  return config;
};

然后安裝 ts-import-plugin
ts-import-plugin 是一個用于按需加載組件代碼和樣式的 TypeScript 插件,現(xiàn)在我們嘗試安裝它并修改 config-overrides.js 文件:

cnpm install add ts-import-plugin --dev // cnpm 安裝 ts-import-plugin

安裝成功后,修改 config-overrides.js 文件:

/* config-overrides.js */
const tsImportPluginFactory = require('ts-import-plugin')
const { getLoader } = require("react-app-rewired");
 
module.exports = function override(config, env) {
  const tsLoader = getLoader(
    config.module.rules,
    rule =>
      rule.loader &&
      typeof rule.loader === 'string' &&
      rule.loader.includes('ts-loader')
  );
 
  tsLoader.options = {
    getCustomTransformers: () => ({
      before: [ tsImportPluginFactory({
        libraryDirectory: 'es',
        libraryName: 'antd',
        style: 'css',
      }) ]
    })
  };
 
  return config;
}

然后移除前面在 src/App.css 里全量添加的 @import '~antd/dist/antd.css'; 樣式代碼,并且修改 src/App.tsx 文件,按照下述模式引入模塊,為了更直觀顯示效果,我增加引入了icon圖標(biāo):

import * as React from 'react';
import { Button,Icon } from 'antd'; //引入了ant design的圖標(biāo)和按鈕
import './App.css';
 
import logo from './logo.svg';
 
class App extends React.Component {
  public render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.tsx</code> and save to reload.
        </p>
        <Button type="primary">AntDesgin按鈕</Button>
        <Icon style={{color:'green',fontSize:'24px',marginLeft:'50px'}} type="check-circle-o" />
      </div>
    );
  }
}
 
export default App;

上述修改完成后,運行項目,發(fā)現(xiàn)達(dá)到了我們預(yù)期的效果,圖示如下:

run.png

至此我們對ant Design的引入就告一段落了。
注:以上參考ant design官方文檔 https://ant.design/docs/react/use-in-typescript-cn

四、修改項目目錄

按照個人習(xí)慣,我們新建assets文件夾以便于管理,修改后項目目錄如下圖所示:
注:修改完成后,別忘記修改 App.tsx、index.tsx 文件中的import路徑哦

list.png

五、引入scss\color{red}{(不想使用scss可跳過本步驟)}

由于本菜喜歡scss的書寫方便,由此引入,習(xí)慣css的同學(xué)可以不使用
1、安裝 sass-loader node-sass 依賴:

cnpm install add sass-loader node-sass --save-dev //安裝 sass-loader node-sass 依賴

\color{red}{但是~~(又來了),此時我們會發(fā)現(xiàn),項目中并沒有 webpack.config.js 這個文件,那么這個文件到底在哪呢?稍等,我去截個圖。。。}

source.png

path.png

好了,通過上述步驟我們找到了webpack的配置文件,其實我們通過package.json中的start命令可以發(fā)現(xiàn)點端倪,其實webpack只是被封裝起來了,
接下來我們就修改 webpack.config.dev.js 文件:

{
   test: /\.css$/,
   use: [
      ...
   ],
},
{
    test: /\.scss$/,
    loaders: ['style-loader', 'css-loader', 'sass-loader'],
}, //增加scss的規(guī)則

修改完成之后我們在 assets 文件夾下創(chuàng)建新的 scss 文件夾,并手動創(chuàng)建 App.scss 文件及 index.scss 文件:

創(chuàng)建完成后將 App.cssindex.scss 文件中的代碼直接復(fù)制進(jìn)對應(yīng)的scss文件,然后刪除css文件夾,操作完成后的項目目錄如下:

\color{red}{注:別忘記修改App.tsx及index.tsx中的引用哦}

image.png

上述修改完成后,重啟項目發(fā)現(xiàn)scss成功引入!

六、創(chuàng)建Spring-Boot項目并整合Mybatis

本菜在此就不做詳細(xì)贅述了,想必用java的同學(xué)都會,不會的話參考下面鏈接:
1、spring-boot 1.5.*版本項目搭建及整合mybatis (https://blog.csdn.net/winter_chen001/article/details/77249029
2、spring-boot 2.0+版本項目搭建及整合mybatis (https://blog.csdn.net/Winter_chen001/article/details/80010967

七、編寫登錄所需接口

代碼如下(僅貼出controller層~):

package com.ycgame.controller;
 
import com.ycgame.model.Result;
import com.ycgame.model.User;
import com.ycgame.service.user.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
 
import static com.ycgame.utils.SetResultInfo.setResultInfo;
 
/**
 * Created by Administrator on 2018/7/16.
 */
@Controller
@RequestMapping(value = "/user")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @ResponseBody
    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpServletRequest request){
        System.out.println(user.getUsername());
        Result result=userService.findByUsername(user);
        if(result.getCode()==0){
            HttpSession session=request.getSession();
            session.setAttribute("user",user);
        }
        return result;
    }
 
    @ResponseBody
    @GetMapping("/loginState")
    public Result loginState(HttpServletRequest request){
        HttpSession session=request.getSession();
        User user=(User)session.getAttribute("user");
        Result result;
        if(user!=null){
            List<Object> dataList=new ArrayList<Object>();
            dataList.add(user);
            result=setResultInfo(0,dataList,"已登錄");
        }else{
            result=setResultInfo(103,null,"未登錄");
        }
        return result;
    }
 
    @ResponseBody
    @GetMapping("/loginOut")
    public Result loginOut(HttpServletRequest request){
        HttpSession session=request.getSession();
        session.removeAttribute("user");
        Result result=setResultInfo(0,null,null);
        return result;
    }
}

上述接口功能分別為:登錄、獲取登錄狀態(tài)、退出登錄

八、引入React-router及axios

安裝 react-router 依賴,本菜使用的是V4版本,各位可自行選擇

cnpm install react-router-dom --save-dev //cnpm安裝react-router-dom(react-router V4版本)
 
cnpm install @types/react-router-dom --save-dev 

為了實現(xiàn)類似 vue-router 中編程式導(dǎo)航的功能,本菜參考(https://segmentfault.com/a/1190000011137828)的第三種方案(主要剛接觸,第一種方法不太會。。而且貌似挺麻煩。。勿噴。。)

1、安裝history依賴
cnpm install @types/history --save-dev //安裝histroy依賴
2、自己創(chuàng)建一個 history.ts/.js 文件,文件類型和位置看個人喜好,代碼如下:
import createHistory from 'history/createBrowserHistory';
 
export default createHistory();

經(jīng)過以上步驟,我們的 react-router 就算成功引入了。下面開始引入axios

cnpm install axios --save-dev //安裝axios

\color{red}{(可跳過)}安裝完成后,本菜對axios發(fā)送請求做了簡單的封裝(utils.ts),代碼如下:

//utils.ts
 
import axios from 'axios'
 
const utils ={
    axiosMethod: (config:any) => {
        axios({
            method: config.method,
            url: config.url,
            params: config.params ? config.params : null,
            data: config.data ? config.data : null,
        }).then(config.callback).catch(config.catch ? config.catch : () => {})
    }
}
 
export default utils

\color{red}{注:axios發(fā)送請求時,post請求與get請求參數(shù)對象名是不一樣的,post請求的參數(shù)對象為data,而get為params~}

\color{red}{(可跳過)}本菜為了請求統(tǒng)一管理新建了一個 axiosRequestConfig.ts 文件,代碼如下:

//axiosRequestConfig.ts
 
const config={
    //登錄校驗
    doLoginConfig:{
        method:'post',
        url:'/user/login'
    },
    //登錄狀態(tài)驗證
    loginStateConfig:{
        method:'get',
        url:'/user/loginState'
    },
    //退出登錄
    loginOutConfig:{
        method:'get',
        url:'/user/loginOut'
    }
}
 
export default config

經(jīng)過上述步驟,我們成功引入了react-routeraxios,本菜當(dāng)前的項目目錄如下:

image.png

九、配置webpack代理

由于spring-boot運行在8080端口,而我們的react項目則是3000端口,因此存在跨域問題,那么我們?nèi)绾闻渲脀ebpack的代理呢,下面上代碼:

首先按照 第五步 的方法找到 webpackDevServer.config.js 文件(node_modules =>react-scripts-ts =>config=>webpackDevServer.config.js ),修改為以下內(nèi)容:

...
 
https: protocol === 'https',
    host: host,
    overlay: true,
    historyApiFallback: {
      // Paths with dots should still use the history fallback.
      // See https://github.com/facebookincubator/create-react-app/issues/387.
      disableDotRule: true,
    },
    public: allowedHost,
    proxy: {
      '/user/*': {
        target: 'http://localhost:8080/', //本地后臺的地址
        secure: false,
        changeOrigin: true
    },
},
 
before(app){
 
...

經(jīng)過上述步驟,我們就能放心的擼代碼了,再也不用擔(dān)心跨域請求的問題啦~~

十、開始擼碼

1、新建 Login.tsx 文件,文件位置看個人習(xí)慣,本菜直接放在src下面,使用ant design表單組件,代碼如下:

//Login.tsx
 
import * as React from 'react';
import './assets/scss/Login.scss';
import utils from './utils/utils'
import requestConfig from './utils/axiosRequestConfig'
import { Form, Icon, Input, Button, Checkbox } from 'antd';
import history from './utils/history';
 
 
const FormItem = Form.Item;
 
class LoginForm extends React.Component {
  constructor(props: any) {
    super(props);
    this.state = {
      loginForm: {
        username: '',
        password: ''
      }
    }
    this.handleInputChange = this.handleInputChange.bind(this);
    this.submitForm = this.submitForm.bind(this);
  }
 
  handleInputChange(event: any): void {
    const target = event.target;
    const value = target.value;
    const name = target.name;
    const tempObj = { ...this.state['loginForm'] };
    tempObj[name] = value;
    this.setState({
      loginForm: tempObj
    })
  }
 
  submitForm(e: any) {
    e.preventDefault();
    this.props['form'].validateFields((err: any, values: any) => {
      if (!err) {
        let doLoginConfig = requestConfig.doLoginConfig;
        let config = {
          data: { t: new Date().getTime(), ...this.state['loginForm'] },
          callback: (response: any) => {
            if (response.data.code == 0) {
              history.push('/home');
            }
          }
        }
        let finalConfig = { ...doLoginConfig, ...config };
        utils.axiosMethod(finalConfig);
      }
    });
  }
 
  render(): any {
    const { getFieldDecorator } = this.props['form'];
    return (
      <Form onSubmit={this.submitForm} className="login-form">
        <FormItem>
          {getFieldDecorator('userName', {
            rules: [{ required: true, message: '請輸入用戶名', whitespace: true }],
          })(
            <Input name='username' onChange={this.handleInputChange} prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="請輸入用戶名" />
          )}
        </FormItem>
        <FormItem>
          {getFieldDecorator('password', {
            rules: [{ required: true, message: '請輸入密碼', whitespace: true }],
          })(
            <Input name='password' onChange={this.handleInputChange} prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="請輸入密碼" />
          )}
        </FormItem>
        <FormItem>
          {getFieldDecorator('remember', {
            valuePropName: 'checked',
            initialValue: true,
          })(
            <Checkbox style={{ color: 'white' }}>記住我</Checkbox>
          )}
          <a className="login-form-forgot" href="">忘記密碼</a>
          <Button type="primary" htmlType="submit" className="login-form-button">
            登錄
          </Button>
          <a href="">前往注冊</a>
        </FormItem>
      </Form>
    );
  }
}
 
const LoginFormComponent = Form.create()(LoginForm);
 
class Login extends React.Component {
  render() {
    return (
      <div className="loginMain">
        <div className="login">
          <h2 style={{ color: 'white', textAlign: 'center' }}>用戶登錄</h2>
          <LoginFormComponent />
        </div>
      </div>
    );
  }
}
 
export default Login;

2、在assets/scss文件夾下新建 Login.scss 的樣式文件,代碼如下:

//Login.scss
 
h2 {
    font-size: 20px;
}
 
.loginMain{
    height: 100%;
    background: url('../images/bg.jpg') no-repeat center center fixed;
    background-size: cover;
}
 
.login{
    width:350px;
    height:330px;
    background: rgba($color: #000, $alpha: .4);
    border-radius: 20px;
    margin: auto;
    padding: 25px;
    color: white;
    position: absolute;
    left: 0 ;
    right: 0;
    top:25%;
}
 
.login-form {
    max-width: 300px;
}
 
.login-form-forgot {
    float: right;
}
 
.login-form-button {
    width: 100%;
}

3、修改 index.tsx 文件,代碼如下:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
import Login from './Login';
import registerServiceWorker from './registerServiceWorker';
import { Router, Route } from 'react-router-dom';
import history from './utils/history';
import requestConfig from './utils/axiosRequestConfig'
import utils from './utils/utils'
 
import './assets/scss/index.scss';
 
 
class Model extends React.Component {
  constructor(props: any) {
    super(props);
  }
 
  componentDidMount() {
    const loginStateConfig = requestConfig.loginStateConfig;
    const config = {
      param: { t: new Date().getTime() },
      callback: (response: any) => {
        if (response.data.code == 0) {
          history.push('/home');
        } else {
          history.push('/');
        }
      }
    }
    const finalConfig = { ...loginStateConfig, ...config };
    utils.axiosMethod(finalConfig);
  }
 
  render() {
    return (
      <div style={{ height: '100%' }}>
        <Route exact={true} path="/" component={Login} />
        <Route exact={true} path="/home" component={App} />
      </div>
    )
  }
}
 
ReactDOM.render(
  <Router history={history}>
    <Model />
  </Router>
  ,document.getElementById('root') as HTMLElement
);
registerServiceWorker();

4、給 App.tsx 增加退出登錄按鈕,修改后代碼如下:

import './assets/scss/App.scss';
 
import * as React from 'react';
import history from './utils/history';
import requestConfig from './utils/axiosRequestConfig'
import utils from './utils/utils'
import { Button, Icon } from 'antd';
import logo from './assets/images/logo.svg';
 
class App extends React.Component {
  constructor(props: any) {
    super(props);
    this.loginOutMethod = this.loginOutMethod.bind(this);
  }
 
  loginOutMethod() {
    const loginOutConfig = requestConfig.loginOutConfig;
    const config = {
      param: { t: new Date().getTime() },
      callback: (response: any) => {
        if (response.data.code == 0) {
          history.push('/');
        }
      }
    }
    const finalConfig = { ...loginOutConfig, ...config };
    utils.axiosMethod(finalConfig);
  }
 
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.tsx</code> and save to reload.
        </p>
        <Button type="primary">AntDesgin按鈕</Button>
        <Icon style={{ color: 'green', fontSize: '24px', marginLeft: '50px' }} type="check-circle-o" />
        <br/>
        <Button style={{marginTop:'30px'}} onClick={this.loginOutMethod}>退出登錄</Button>
      </div>
    );
  }
}
 
export default App;

以上我們的代碼部分就暫時擼完了現(xiàn)在我們啟動spring-boot項目及npm start運行react項目,效果如下(不會做gif理解一下。。。):

image.png

image.png

輸入正確的用戶名密碼后點擊登錄會跳轉(zhuǎn)到localhost:3000/home頁面(主頁),點擊退出登錄會返回登錄頁測試效果與我們希望的結(jié)果一致。

結(jié)束語

看到這里就結(jié)束了?。??這時候就會有小伙伴說了,Redux被你吃了么?全程沒看到啊。。說來慚愧,Redux整合本菜倒是弄得差不多了,然而由于剛接觸,后續(xù)有空再更新吧。。

以上就是本菜最近幾天的摸索成果,實現(xiàn)起來可能不夠優(yōu)雅,希望各位React大佬勿噴,畢竟我只是一個React的萌新。。。最后希望本文對各位同學(xué)入坑React能有所幫助,如果能幫到您,是我的榮幸~~

\color{red}{最后附上github地址:}https://github.com/xueyecheng/react-antd-ts-demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容