# [2]Django Rest API + React.js + Redux + webpack 前後端整合
###### tags: `python` `Django` `Django Rest framework` `React.js` `redux` `webpack`
> [time= 2019 11 27 ]
> 原文 & 參考:
> https://www.youtube.com/watch?v=GieYIzvdt2U&list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60&index=2
<br>
## Implementing React
*終端機目前位置 `~/django_rest_api_react/leadmanager/`*
### setting up React and webpack
新增 Django 應用程式 frontend
```=
$ python manage.py startapp frontend
```
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──tests.py
│ │ └──views.py
│ │
│ ├──leadmanager
│ ├──leads
│ ├──db.sqlite3
│ └──manage.py
│
├──Pipfile
└──Pipfile.lock
```
<br><br><br>
建立目錄結構來保存 React components:
```=
$ mkdir -p ./frontend/src/components
$ mkdir -p ./frontend/{static,templates}/frontend
```
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ │ └──components
│ │ │
│ │ ├──static
│ │ │ └──frontend
│ │ │
│ │ ├──templates
│ │ │ └──frontend
│ │ │
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──tests.py
│ │ └──views.py
│ │
│ ├──leadmanager
│ ├──leads
│ ├──db.sqlite3
│ └──manage.py
│
├──Pipfile
└──Pipfile.lock
```
<br><br><br>
現在要從目前位置 `~/django_rest_api_react/leadmanager/` 移置到上一層資料夾
```=
$ cd ..
```
*終端機目前位置 `~/django_rest_api_react/`*
<br><br><br>
npm版本:
```=
$ npm -- version
```
>*6.9.0*
<br><br><br>
初始化環境:
```=
$ npm init -y
```
<br><br><br>
安裝 webpack 和 webpack cli:
```=
$ npm i -D webpack webpack-cli
```
>*webpack@4.41.2*
>*webpack-cli@3.3.10*
<br><br><br>
安裝 babel 來編譯我們的代碼:
```=
$ npm i -D @babel/core babel-loader @babel/preset-env @babel/preset-react babel-plugin-transform-class-properties
```
>*babel-loader@8.0.6*
>*@babel/preset-react@7.7.4*
>*babel-plugin-transform-class-properties@6.24.1*
>*@babel/core@7.7.4*
>*@babel/preset-env@7.7.4*
<br><br><br>
安裝 React 和 prop-types:
```=
$ npm i react react-dom prop-types
```
>*prop-types@15.7.2*
>*react@16.12.0*
>*react-dom@16.12.0*
<br><br><br>
配置Babel,新增一個檔案命名為 `./.babelrc` 在裡面寫入:
```bable=
{
"presets": [
"@babel/preset-env", "@babel/preset-react"
],
"plugins": [
"transform-class-properties"
]
}
```
資料結構
```
django_rest_api_react
├──leadmanager
├──node_modules
├──.babelrc
├──package-lock.json
├──package.json
├──Pipfile
└──Pipfile.lock
```
<br><br><br>
配置babel-loader,新增一個檔案命名為 `./webpack.config.js` 在裡面寫入:
```javascript=
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
```
資料結構
```
django_rest_api_react
├──leadmanager
├──node_modules
├──.babelrc
├──package-lock.json
├──package.json
├──Pipfile
├──Pipfile.lock
└──webpack.config.js
```
<br><br><br>
現在開啟 `package.json` 配置 scripts:
將
```json=
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
```
改成
```json=
"scripts": {
"dev": "webpack --mode development ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js",
"build": "webpack --mode production ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js"
}
```
<br><br><br>
### the React frontend
新增一個檔案命名為 `./leadmanager/frontend/src/index.js` 在裡面寫入:
```javascript=
import App from './components/App';
```
<br><br><br>
新增一個檔案命名為 `./leadmanager/frontend/src/components/App.js` 在裡面寫入:
```javascript=
import React, { Component } from 'react';
import ReactDom from 'react-dom';
export class App extends Component {
render() {
return <h1>React App</h1>
}
}
ReactDom.render(<App />, document.getElementById('app'));
```
<br><br><br>
使用 Bootstrap
新增一個檔案命名為 `./leadmanager/frontend/templates/frontend/index.html` 在裡面寫入:
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://bootswatch.com/4/cosmo/bootstrap.min.css">
<title>Lead Manager</title>
</head>
<body>
<div id="app"></div>
{% load static %}
<script src="{% static "frontend/main.js" %}"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.0/js/bootstrap.min.js"
integrity="sha384-3qaqj0lc6sV/qpzrc1N5DC6i1VRn/HyX4qdPaiEFbn54VjQBEU341pvjz7Dv3n6P"
crossorigin="anonymous"></script>
</body>
</html>
```
`./frontend/static/frontend/main.js` 由 webpack 建置時產生
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ │ └──components
│ │ │ │ └──App.js
│ │ │ │
│ │ │ └──index.js
│ │ │
│ │ ├──static
│ │ ├──templates
│ │ │ └──frontend
│ │ │ └──index.html
│ │ │
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──tests.py
│ │ └──views.py
│ │
│ ├──leadmanager
│ ├──leads
│ ├──db.sqlite3
│ └──manage.py
│
├──node_modules
├──.babelrc
├──package-lock.json
├──package.json
├──Pipfile
├──Pipfile.lock
└──webpack.config.js
```
<br><br><br>
開起 `./leadmanager/leadmanager/settings.py` 加入應用程式 `frontend` 在 `INSTALLED_APPS`:
```python=
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'leads',
'frontend', # enable the frontend app
]
```
<br><br><br>
creating a views,讓 django 可以導向 `index.html`
開起 `./leadmanager/frontend/views.py` 加入:
```python=
from django.shortcuts import render
# new add
def index(request):
return render(request, "frontend/index.html")
```
<br><br><br>
新增一個檔案命名為` ./leadmanager/frontend/urls.py` 在裡面寫入:
```python=
from django.urls import path
from . import views
urlpatterns = [
path('', views.index)
]
```
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ ├──static
│ │ ├──templates
│ │ ├──__init__.py
│ │ ├──admin.py
│ │ ├──apps.py
│ │ ├──models.py
│ │ ├──tests.py
│ │ ├──urls.py
│ │ └──views.py
│ │
│ ├──leadmanager
│ ├──leads
│ ├──db.sqlite3
│ └──manage.py
│
├──node_modules
├──.babelrc
├──package-lock.json
├──package.json
├──Pipfile
├──Pipfile.lock
└──webpack.config.js
```
<br><br><br>
開啟 `./leadmanager/leadmanager/urls.py` 加入:
```python=
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('frontend.urls')), # new add
path('', include('leads.urls')),
]
```
<br><br><br>
Run webpack:
*終端機目前位置 `~/django_rest_api_react/`*
```=
$ npm run dev
```
執行完會在 `./leadmanager/frontend/static/frontend/` 新增一個 main.js
現在執行 django 查看 React 是否建置成功
```=
$ cd leadmanager
$ python manage.py runserver
```
前往 http://127.0.0.1:8000/

<br><br><br>
creating a Header
新增一個檔案命名為 `./leadmanager/frontend/src/components/layout/Header.js` 在裡面寫入:
```javascript=
import React, { Component } from 'react'
export class Header extends Component {
render() {
return (
<nav className="navbar navbar-expand-sm navbar-light bg-light">
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarTogglerDemo01">
<a className="navbar-brand" href="#">Lead Manager</a>
<ul className="navbar-nav ml-auto mt-2 mt-lg-0">
</ul>
</div>
</nav>
)
}
}
export default Header
```
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ │ ├──components
│ │ │ │ ├──layout
│ │ │ │ │ └──Header.js
│ │ │ │ │
│ │ │ │ └──App.js
│ │ │ │
│ │ │ └──index.js
│ │ │
...
```
<br><br><br>
開啟 `./leadmanager/frontend/src/components/App.js` 引入剛剛新建的 Header.js:
```javascript=
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import Header from './layout/Header'; // new add
export class App extends Component {
render() {
return (
<Header /> // modify
)
}
}
ReactDom.render(<App />, document.getElementById('app'));
```
開新的一個終端機(不要關掉 django server), Run webpack:
*終端機目前位置 `~/django_rest_api_react/`*
```=
npm run dev
```
查看結果

<br><br><br>
為了不要每次修改 JavaScript 或 React 都要執行 `npm run dev` ,
在 `./package.json` 中的 `scripts` 裡加入 `--watch` :
```javascript=
"scripts": {
"dev": "webpack --mode development --watch ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js",
"build": "webpack --mode production ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js"
},
```
現在執行 `npm run dev` 後就會持續偵測你是否有改程式,當你存檔後會自動編譯
*(關閉按 `control + c`)*
<br><br><br>
新增一個檔案命名為 `./leadmanager/frontend/src/components/leads/Form.js`
在裡面寫入:
```javascript=
import React, { Component } from 'react'
export class Form extends Component {
render() {
return (
<div>
<h1>Add Leads Form</h1>
</div>
)
}
}
export default Form
```
<br><br><br>
新增一個檔案命名為 `./leadmanager/frontend/src/components/leads/Leads.js`
在裡面寫入:
```javascript=
import React, { Component } from 'react'
export class Leads extends Component {
render() {
return (
<div>
<h1>Leads List</h1>
</div>
)
}
}
export default Leads
```
<br><br><br>
新增一個檔案命名為 `./leadmanager/frontend/src/components/leads/Dashboard.js`
在裡面寫入:
```javascript=
import React, { Fragment } from 'react';
import Form from './Form';
import Leads from './Leads';
export default function Dashboard() {
return (
<Fragment>
<Form />
<Leads />
</Fragment>
)
};
```
資料結構
```
django_rest_api_react
├──leadmanager
│ ├──frontend
│ │ ├──migrations
│ │ ├──src
│ │ │ ├──components
│ │ │ │ ├──layout
│ │ │ │ ├──leads
│ │ │ │ │ ├──Dashborad.js
│ │ │ │ │ ├──Form.js
│ │ │ │ │ └──Leads.js
│ │ │ │ │
│ │ │ │ └──App.js
│ │ │ │
│ │ │ └──index.js
│ │ │
...
```
<br><br><br>
開啟 `./leadmanager/frontend/src/components/App.js` 引入剛剛新建的 Dashboard.js:
```javascript=
import React, { Component, Fragment } from 'react'; // modify
import ReactDom from 'react-dom';
import Header from './layout/Header';
import Dashboard from './leads/Dashboard'; // new add
export class App extends Component {
render() {
return (
<Fragment> // new add
<Header />
// new add
<div className="container">
<Dashboard />
</div>
</Fragment> // new add
)
}
}
ReactDom.render(<App />, document.getElementById('app'));
```
<br><br><br>
reload page

<br><br><br>
[[3]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/rksvPD6nB)
[[1]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/Syzvus93S)