Django REST 系列教程(三)(下)

重構(gòu)

終于考完試了。今天有空把這部分完成了,大家久等了。


設(shè)計(jì)

交互還是和以前一樣,只是 API 略微有些變動(dòng)。如果需要 創(chuàng)建時(shí)就運(yùn)行一次代碼 ,我們只需要在對應(yīng)操作之后加上 run 參數(shù)就可以了。 比如 向 /code/?run post 就可以創(chuàng)建并運(yùn)行代碼了,更新并運(yùn)行也是同理。

如果需要單獨(dú)運(yùn)行代碼 向 /run/ post 代碼解可以了,如果需要運(yùn)行特定的實(shí)例,只需使用 get 請求在后面加上 id 參數(shù)就行。比如 GET /run/?id=1 就會(huì)得到代碼實(shí)例 id 為 1 的運(yùn)行結(jié)果。

準(zhǔn)備工作。

創(chuàng)建一個(gè)新的項(xiàng)目 online_python

django-admin startproject online_python

然后在 online_python 項(xiàng)目內(nèi)創(chuàng)建一個(gè) APP

python manage.py startapp backend

然后創(chuàng)建如下的目錄結(jié)構(gòu)

online_python/
    frontend/
        index.js # 空文件
        index.html # 空文件
        vue.js # vue 的源文件
        bootstrap.js # bootstrap 的 js文件
        jquery.js # bootstrap.js 的依賴
        bootstrap.css # bootstrap 核心 css 文件
    backend/
        ... # APP 相關(guān)
    manage.py

編寫配置,把我們的 APP 和 DRF 添加進(jìn)去。

settings.py

...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'backend'
]
...

準(zhǔn)備完畢。

后端開發(fā)

我們還是先從后端寫起。

創(chuàng)建模型:

models.poy


from django.db import models

class Code(models.Model):
    name = models.CharField(max_length=20, blank=True)
    code = models.TextField()
    

在模型創(chuàng)建完成之后,我們需要?jiǎng)?chuàng)建首次遷移。

回到項(xiàng)目根目錄,創(chuàng)建并運(yùn)行遷移,同時(shí)把管理員賬戶創(chuàng)建好。

python manage.py makemigrations
python manage.py migrate

python manage.py craetesuperuser

創(chuàng)建序列化器:

backend 下新建文件 serializers.py

serializers.py

from rest_framework import serializers
from .models import Code

#創(chuàng)建序列化器
class CodeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Code
        fields = '__all__' #序列化全部字段

#用于列表展示的序列化器
class CodeListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Code
        fields = ('id', 'name')

為什么會(huì)有兩個(gè)序列化器?

因?yàn)槲覀冋埱? list 時(shí),我們只需要 Code 實(shí)例的 nameid 字段,在其它的情況下又需要用到全部的字段。所以我們需要兩個(gè)序列化器。

現(xiàn)在就可以開始編寫視圖了。

在頂部引入我們需要的包。

views.py

import subprocess
from django.http import HttpResponse
from django.db import models
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import CodeListSerializer, CodeSerializer
from .models import Code
from rest_framework.authentication import SessionAuthentication

subprocess 用于運(yùn)行客戶端代碼的包。

HttpResponse 用于靜態(tài)文件服務(wù)視圖。

models 主要是為了使用它的 ObjectDoseNotExist 異常。

APIView 最基本的 DRF API 視圖。

Response DRF 響應(yīng)對象。

status DRF 為我們封裝好的狀態(tài)響應(yīng)碼。

CodeSerializer、CodeListSerializer 需要用到的序列化器。

Code Code 模型。

SessionAuthentication 用于編寫禁止 CSRF 的認(rèn)證后端。我們會(huì)在下面詳細(xì)的說明。

我們先把運(yùn)行代碼的 Mixin 給復(fù)制粘貼過來。

views.py

class APIRunCodeMixin(object):
    """
    運(yùn)行代碼操作
    """

    def run_code(self, code):
        """
        運(yùn)行所給的代碼,并返回執(zhí)行結(jié)果
        :params code: str, 需要被運(yùn)行的代碼
        :return: str, 運(yùn)行結(jié)果
        """
        try:
            output = subprocess.check_output(['python', '-c', code],  # 運(yùn)行代碼
                                             stderr=subprocess.STDOUT,  # 重定向錯(cuò)誤輸出流到子進(jìn)程
                                             universal_newlines=True,  # 將返回執(zhí)行結(jié)果轉(zhuǎn)換為字符串
                                             timeout=30)  # 設(shè)定執(zhí)行超時(shí)時(shí)間
        except subprocess.CalledProcessError as e:  # 捕捉執(zhí)行失敗異常
            output = e.output  # 獲取子進(jìn)程報(bào)錯(cuò)信息
        except subprocess.TimeoutExpired as e:  # 捕捉超時(shí)異常
            output = '\r\n'.join(['Time Out!', e.output])  # 獲取子進(jìn)程報(bào)錯(cuò),并添加運(yùn)行超時(shí)提示
        return output  # 返回執(zhí)行結(jié)果

創(chuàng)建 CodeViewSet

views.py

class CodeViewSet(APIRunCodeMixin, ModelViewSet):
    queryset = Code.objects.all()
    serializer_class = CodeSerializer

這是最最基本的 CodeViewSet 。 DRF 的 ViewSet 為我們默認(rèn)編寫好了各個(gè)請求方法對應(yīng)的操作映射。不帶參數(shù)的 get 請求對應(yīng) list 操作,post 請求對應(yīng) create 操作等等。這也是它叫做 ViewSet (視圖集)的原因,它幫我們完成了基本的幾個(gè)視圖原型。ModelViewSet 讓我們可以直接把視圖和模型相關(guān)聯(lián)起來,比如 list 會(huì)直接返回模型序列化之后的結(jié)果,而不需要我們手動(dòng)編寫這些動(dòng)作。

list 默認(rèn)使用的是 serializer_class 指定的序列化器,但是由于我們需要在 list 動(dòng)作的時(shí)候用另一個(gè)序列化器,所以我們需要簡單的重寫這個(gè)動(dòng)作。

views.py

    def list(self, request, *args, **kwargs):
        """
        使用專門的列表序列化器,而非默認(rèn)的序列化器
        """
        serializer = CodeListSerializer(self.get_queryset(), many=True)
        return Response(data=serializer.data)

create 操作需要判斷是否有 run 參數(shù),所以我們也需要重寫 create 操作。

views.py

    def create(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():
            code = serializer.validated_data.get('code')
            serializer.save()
            if 'run' in request.query_params.keys():
                output = self.run_code(code)
                data = serializer.data
                data.update({'output': output})
                return Response(data=data, status=status.HTTP_201_CREATED)
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)

我們知道 Django 會(huì)向視圖函數(shù)默認(rèn)傳入一個(gè) Request 對象,但是這里的 Request 對象是 DRF 的請求對象。

request.query_params 是 DRF 請求對象獲取請求參數(shù)的方式,query_params 保存了所有的請求參數(shù)。

在 Django 的表單中,我們可以使用 form.save() 來直接把數(shù)據(jù)保存到模型中。在序列化器中也是同理,我們可以使用 serializer.save() 把序列化器中的數(shù)據(jù)直接保存到模型中。

同樣的,我們的 update 操作也需要做同樣的事情。

views.py

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.serializer_class(instance, data=request.data)

        if serializer.is_valid():
            code = serializer.validated_data.get('code')
            serializer.save()
            if 'run' in request.query_params.keys():
                output = self.run_code(code)
                data = serializer.data
                data.update({'output': output})
                return Response(data=data, status=status.HTTP_201_CREATED)
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)

get_object 是用于提取當(dāng)前請求對應(yīng)的實(shí)例方法。

我們發(fā)現(xiàn),createupdate 除了在創(chuàng)建序列化器實(shí)例不同之外,我們完全可以把他們的邏輯放在一起。

views.py

    def run_create_or_update(self, request, serializer):
        """
        create 和 update 的共有邏輯,僅僅是簡單的多了 run 參數(shù)的判斷
        """
        if serializer.is_valid():
            code = serializer.validated_data.get('code')
            serializer.save()
            if 'run' in request.query_params.keys():
                output = self.run_code(code)
                data = serializer.data
                data.update({'output': output})
                return Response(data=data, status=status.HTTP_201_CREATED)
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def create(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        return self.run_create_or_update(request, serializer)

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.serializer_class(instance, data=request.data)
        return self.run_create_or_update(request, serializer)

到這里我們主要的 API 就完成了。

現(xiàn)在需要完成可以直接運(yùn)行代碼的 API 。

views.py

class RunCodeAPIView(APIRunCodeMixin, APIView):

    def post(self, request, format=None):
        output = self.run_code(request.data.get('code'))
        return Response(data={'output': output}, status=status.HTTP_200_OK)

    def get(self, request, format=None):
        try:
            code = Code.objects.get(pk=request.query_params.get('id'))
        except models.ObjectDoesNotExist:
            return Response(data={'error': 'Object Not Found'}, status=status.HTTP_404_NOT_FOUND)
        output = self.run_code(code.code)
        return Response(data={'output': output}, status=status.HTTP_200_OK)

接下來完成靜態(tài)文件服務(wù)的請求視圖,把之前寫的代碼復(fù)制粘貼過來,稍稍做一些更改。

views.py

def home(request):
    with open('frontend/index.html', 'rb') as f:
        content = f.read()
    return HttpResponse(content)


def js(request, filename):
    with open('frontend/{}'.format(filename), 'rb') as f:
        js_content = f.read()
    return HttpResponse(content=js_content,
                        content_type='application/javascript') 


def css(request, filename):
    with open('frontend/{}'.format(filename), 'rb') as f:
        css_content = f.read()
    return HttpResponse(content=css_content,
                        content_type='text/css')

完成我們的 url 配置。

urls.py

from django.conf.urls import url, include
from django.contrib import admin
from rest_framework.routers import DefaultRouter
from backend.views import CodeViewSet, RunCodeAPIView, home, js, css

router = DefaultRouter()
router.register(prefix='code', viewset=CodeViewSet, base_name='code')

API_V1 = [url(r'^run/$', RunCodeAPIView.as_view(), name='run')]

API_V1.extend(router.urls)

API_VERSIONS = [url(r'^v1/', include(API_V1))]

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(API_VERSIONS)),
    url(r'^js/(?P<filename>.*\.js)$', js, name='js'),
    url(r'^css/(?P<filename>.*\.css)$', css, name='css'),
    url(r'^$', home, name='home')
]

在之前,我們對于 csrf 的處理都是使用的 csrf_exempt ,現(xiàn)在我們的 API 都是使用 Router 來生成了。該怎么辦呢?

在 Django 中,一個(gè)請求在到達(dá)視圖之前,會(huì)先經(jīng)過中間件的處理。在 DRF 中,所有的請求會(huì)先經(jīng)過認(rèn)證處理,如果請求認(rèn)證通過,則會(huì)讓請求訪問視圖,如果認(rèn)證不通過,請求就無法到達(dá)視圖。所以,我們采用的方法是重寫認(rèn)證。

在 APIView 中,如果提供了 authentication_classes ,則會(huì)使用提供的認(rèn)證后端來進(jìn)行認(rèn)證。如果沒有提供,則會(huì)使用默認(rèn)的認(rèn)證后端。有關(guān)的細(xì)節(jié)我們將會(huì)在之后的章節(jié)中討論,大家就先了解到這里。提供 csrf 驗(yàn)證的是一個(gè)叫做 SessionAuthentication 的認(rèn)證后端,我們需要重新改寫其中驗(yàn)證 csrf 的方法。

views.py

class CsrfExemptSessionAuthentication(SessionAuthentication):
    """
    去除 CSRF 檢查
    """

    def enforce_csrf(self, request):
        return

這樣就完成了。

然后把它放進(jìn)我們視圖中。

整個(gè) views.py 的代碼就是這樣的。

views.py

import subprocess
from django.http import HttpResponse
from django.db import models
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import CodeListSerializer, CodeSerializer
from .models import Code
from rest_framework.authentication import SessionAuthentication


class CsrfExemptSessionAuthentication(SessionAuthentication):
    """
    去除 CSRF 檢查
    """

    def enforce_csrf(self, request):
        return


class APIRunCodeMixin(object):
    """
    運(yùn)行代碼操作
    """

    def run_code(self, code):
        """
        運(yùn)行所給的代碼,并返回執(zhí)行結(jié)果
        :params code: str, 需要被運(yùn)行的代碼
        :return: str, 運(yùn)行結(jié)果
        """
        try:
            output = subprocess.check_output(['python', '-c', code],  # 運(yùn)行代碼
                                             stderr=subprocess.STDOUT,  # 重定向錯(cuò)誤輸出流到子進(jìn)程
                                             universal_newlines=True,  # 將返回執(zhí)行結(jié)果轉(zhuǎn)換為字符串
                                             timeout=30)  # 設(shè)定執(zhí)行超時(shí)時(shí)間
        except subprocess.CalledProcessError as e:  # 捕捉執(zhí)行失敗異常
            output = e.output  # 獲取子進(jìn)程報(bào)錯(cuò)信息
        except subprocess.TimeoutExpired as e:  # 捕捉超時(shí)異常
            output = '\r\n'.join(['Time Out!', e.output])  # 獲取子進(jìn)程報(bào)錯(cuò),并添加運(yùn)行超時(shí)提示
        return output  # 返回執(zhí)行結(jié)果


class CodeViewSet(APIRunCodeMixin, ModelViewSet):
    queryset = Code.objects.all()
    serializer_class = CodeSerializer
    authentication_classes = (CsrfExemptSessionAuthentication,)

    def list(self, request, *args, **kwargs):
        """
        使用專門的列表序列化器,而非默認(rèn)的序列化器
        """
        serializer = CodeListSerializer(self.get_queryset(), many=True)
        return Response(data=serializer.data)

    def run_create_or_update(self, request, serializer):
        """
        create 和 update 的共有邏輯,僅僅是簡單的多了 run 參數(shù)的判斷
        """
        if serializer.is_valid():
            code = serializer.validated_data.get('code')
            serializer.save()
            if 'run' in request.query_params.keys():
                output = self.run_code(code)
                data = serializer.data
                data.update({'output': output})
                return Response(data=data, status=status.HTTP_201_CREATED)
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def create(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        return self.run_create_or_update(request, serializer)

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.serializer_class(instance, data=request.data)
        return self.run_create_or_update(request, serializer)


class RunCodeAPIView(APIRunCodeMixin, APIView):
    authentication_classes = (CsrfExemptSessionAuthentication,)

    def post(self, request, format=None):
        output = self.run_code(request.data.get('code'))
        return Response(data={'output': output}, status=status.HTTP_200_OK)

    def get(self, request, format=None):
        try:
            code = Code.objects.get(pk=request.query_params.get('id'))
        except models.ObjectDoesNotExist:
            return Response(data={'error': 'Object Not Found'}, status=status.HTTP_404_NOT_FOUND)
        output = self.run_code(code.code)
        return Response(data={'output': output}, status=status.HTTP_200_OK)


def home(request):
    with open('frontend/index.html', 'rb') as f:
        content = f.read()
    return HttpResponse(content)


def js(request, filename):
    with open('frontend/{}'.format(filename), 'rb') as f:
        js_content = f.read()
    return HttpResponse(content=js_content,
                        content_type='application/javascript')  


def css(request, filename):
    with open('frontend/{}'.format(filename), 'rb') as f:
        css_content = f.read()
    return HttpResponse(content=css_content,
                        content_type='text/css')

DRF 還為我們提供了可視化的 API 。運(yùn)行開發(fā)服務(wù)器,直接訪問 http://127.0.0.1:8000/api/v1/

你會(huì)看到這樣的頁面

API 根路徑

DRF 為我們列出了 code API ,點(diǎn)擊連接地址,我們就可以在跳轉(zhuǎn)的頁面中直接進(jìn)行相關(guān)的操作。比如用 POST 創(chuàng)建一個(gè)新的代碼實(shí)例。

post 示例

提交之后,我們來到了這樣的頁面。


提交結(jié)果

然后我們直接在瀏覽器中訪問這個(gè)實(shí)例的地址,在這里,我的 id 是 46 ,你們根據(jù)自己的實(shí)例創(chuàng)建 id 來訪問。

實(shí)例詳情

這個(gè)可視化 API 有什么用呢?最大的用處莫過于在前端開發(fā)的時(shí)候,看看不同的接口會(huì)返回什么樣的數(shù)據(jù)類型,具體的格式是什么,這樣前端才好對相應(yīng)的數(shù)據(jù)做正確的處理。當(dāng)前端在開發(fā)時(shí)對接口有什么疑問,可以自行用它來做實(shí)驗(yàn)。方便了前后端的接口協(xié)作處理。

前端開發(fā)

首先在入口 html 頁面中寫好組件入口。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>在線 Python 解釋器</title>
    <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
</head>
<body>
<div id="app"></div>
<script src="js/jquery.js"></script>
<script src="js/bootstrap.js"></script>
<script src="js/vue.js"></script>
<script src="js/index.js"></script>
</body>
</html>

接下來的工作都將會(huì)在 index.js 中完成。

先編寫好 API :
index.js

let api = {
    v1: {
        run: function () {
            return '/api/v1/run/'
        },
        code: {
            list: function () {
                return '/api/v1/code/'
            },
            create: function (run = false) {
                let base = '/api/v1/code/';
                return run ? base + '?run' : base
            },
            detail: function (id, run = false) {
                let base = `/api/v1/code/${id}/`;
                return run ? base + '?run' : base
            },
            remove: function (id) {
                return api.v1.code.detail(id, false)
            },
            update: function (id, run = false) {
                return api.v1.code.detail(id, run)
            }
        }
    }
}

我們還需要 Store 來管理狀態(tài)。我們知道 Store 是管理和儲(chǔ)存公共數(shù)據(jù)的地方,同時(shí)我們對于 API 的操作其實(shí)就是對于數(shù)據(jù)的操作,我們應(yīng)該把所有直接和 API 相關(guān)的請求和操作都定義在這里。

index.js

let store = {
    state: {
        list: [],
        code: '',
        name: '',
        id: '',
        output: ''
    },
    actions: {
        run: function (code) { //運(yùn)行代碼
            $.post({
                url: api.v1.run(),
                data: {code: code},
                dataType: 'json',
                success: function (data) {
                    store.state.output = data.output
                }
            })
        },
        runDetail: function (id) { //運(yùn)行特定的代碼
            $.getJSON({
                url: api.v1.run() + `?id=${id}`,
                success: function (data) {
                    store.state.output = data.output
                }
            })
        },
        freshList: function () { //獲得代碼列表
            $.getJSON({
                url: api.v1.code.list(),
                success: function (data) {
                    store.state.list = data
                }
            })
        },
        getDetail: function (id) {//獲得特定的代碼實(shí)例
            $.getJSON({
                url: api.v1.code.detail(id),
                success: function (data) {
                    store.state.id = data.id;
                    store.state.name = data.name;
                    store.state.code = data.code;
                    store.state.output = '';
                }
            })
        },
        create: function (run = false) { //創(chuàng)建新代碼
            $.post({
                url: api.v1.code.create(run),
                data: {
                    name: store.state.name,
                    code: store.state.code
                },
                dataType: 'json',
                success: function (data) {
                    if (run) {
                        store.state.output = data.output
                    }
                    store.actions.freshList()
                }
            })
        },
        update: function (id, run = false) { //更新代碼
            $.ajax({
                url: api.v1.code.update(id, run),
                type: 'PUT',
                data: {
                    code: store.state.code,
                    name: store.state.name
                },
                dataType: 'json',
                success: function (data) {
                    if (run) {
                        store.state.output = data.output
                    }
                    store.actions.freshList()
                }
            })
        },
        remove: function (id) { //刪除代碼
            $.ajax({
                url: api.v1.code.remove(id),
                type: 'DELETE',
                dataType: 'json',
                success: function (data) {
                    store.actions.freshList()
                }
            })
        }
    }
}

store.actions.freshList() // Store的初始化工作,先獲取代碼列表

相比我們之前結(jié)構(gòu),把統(tǒng)一的數(shù)據(jù)操作都放在 Store 中,這樣就不會(huì)顯得很混亂,并且 API 也簡潔了不少。

下面改編寫組件了。

在寫代碼的時(shí)候,我們需要按照“人類思維”來寫代碼,但是在具體組織代碼的時(shí)候,我們需要按照“程序思維”來組織代碼。根組件會(huì)引用前面的組件,但是前面的組件我們都還沒有實(shí)現(xiàn),所以根組件事實(shí)上是應(yīng)該放在所有代碼之后的。所以大家在寫的時(shí)候注意自己代碼的該寫在哪里。不要代碼全對而產(chǎn)生 undefined 錯(cuò)誤。

先編寫根組件:

index.js

let root = new Vue({ //根組件,整個(gè)頁面入口
    el: '#app',
    template: '<app></app>',
    components: {
        'app': app
    }
})

app 是我們的頁面框架,我們在下面實(shí)現(xiàn)它。

然后在 root 上面 編寫頁面框架:

index.js

let app = { //整體頁面布局
    template: `
        <div class="continer-fluid">
            <div class="row text-center h1">
                在線 Python 解釋器
            </div>
            <hr>
            <div class="row">
                <div class="col-md-3">
                    <code-list></code-list>
                </div>
                <div class="col-md-9">
                    <div class="container-fluid">
                        <div class="col-md-6">
                            <p class="text-center h3">請?jiān)谙路捷斎氪a:</p>
                            <code-input></code-input>
                            <hr>
                            <code-options></code-options>
                        </div>
                        <p class="text-center h3">輸出</p>
                        <div class="col-md-6">
                            <code-output></code-output>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `,
    components: {
        'code-input': input,
        'code-list': list,
        'code-options': options,
        'code-output': output
    }
}

app 組件是所有組件被組織在一起的地方,但是用到的組件都還沒有實(shí)現(xiàn),所以還沒有被實(shí)現(xiàn)的組件代碼都應(yīng)該放在它的 上面。

list 組件:

index.js

let list = { //代碼列表組件
    template: `
    <table class="table table-striped">
        <thead> 
            <tr>
                <th class="text-center">文件名</th> 
                <th class="text-center">選項(xiàng)</th> 
            </tr>
        </thead>
        <tbody>
            <tr v-for="item in state.list">
            <td class="text-center">{{ item.name }}</td>
            <td>
                <button class='btn btn-primary' @click="getDetail(item.id)">查看</button>
                <button class="btn btn-primary" @click="run(item.id)">運(yùn)行</button>
                <button class="btn btn-danger" @click="remove(item.id)">刪除</button>
            </td>
            </tr>
        </tbody> 
    </table>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        getDetail(id) {
            store.actions.getDetail(id)
        },
        run(id) {
            store.actions.runDetail(id)
        },
        remove(id) {
            store.actions.remove(id)
        }
    }
}

options 組件:

index.js

let options = {//代碼選項(xiàng)組件
    template: `
    <div style="display: flex;
         justify-content: space-around;
         flex-wrap: wrap" >
        <button class="btn btn-primary" @click="run(state.code)">運(yùn)行</button>
        <button class="btn btn-primary" @click="update(state.id)">保存</button>
        <button class="btn" @click="update(state.id, true)">保存并運(yùn)行</button>
        <button class="btn btn-primary" @click="newOptions">New</button>
    </div>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        run(code) {
            store.actions.run(code)
        },
        update(id, run = false) {
            if (typeof id == 'string') {
                store.actions.create(run)
            } else {
                store.actions.update(id, run)
            }
        },
        newOptions() {
            this.state.name = '';
            this.state.code = '';
            this.state.id = '';
            this.state.output = '';
        }
    }
}

input 組件:

index.js

let input = { //代碼輸入組件
    template: `
    <div class="form-group">
        <textarea 
        class="form-control" 
        id="input"
        :value="state.code"
        @input="inputHandler"></textarea> 
        <label for="code-name-input">代碼片段名</label>
        <p class="text-info">如需保存代碼,建議輸入代碼片段名</p>
        <input 
        type="text" 
        class="form-control" 
        :value="state.name"
        @input="(e)=> state.name = e.target.value">
    </div>
    `,
    data() {
        return {
            state: store.state
        }
    },
    methods: {
        flexSize(selector) {
            let ele = $(selector);
            ele.css({
                'height': 'auto',
                'overflow-y': 'hidden'
            }).height(ele.prop('scrollHeight'))
        },
        inputHandler(e) {
            this.state.code = e.target.value;
            this.flexSize(e.target)
        }
    }
}

我們把之前的 flexSize直接復(fù)制粘貼過來了。這樣做的好處是,和組件有關(guān)的東西都在組件內(nèi),而不需要去到處找。

output 組件:

index.js

let output = { //代碼輸出組件
    template: `
    <textarea disabled 
    class="form-control text-center">{{ state.output }}</textarea>
    `,
    data() {
        return {
            state: store.state
        }
    },
    updated() {
        let ele = $(this.$el);
        ele.css({
            'height': 'auto',
            'overflow-y': 'hidden'
        }).height(ele.prop('scrollHeight'))
    }
}

在這里我們選擇了完全不同的動(dòng)態(tài)大小方案。在 input 組件中,我們選擇的是使用 input 事件來觸發(fā)調(diào)整大小的函數(shù)。而在這里,我們選擇在 output 組件更新完畢之后之后再觸發(fā)這個(gè)函數(shù)。

.$el 是這個(gè)組件最外層的 html 標(biāo)簽。在這里就是我們的 textarea 標(biāo)簽了。

如果我們需要組件在更新完畢之后做什么事情,就在選項(xiàng)對象里定義 updated 屬性,組件會(huì)在更新完畢后調(diào)用它。這屬于組件的生命周期的一部分。

生命周期有點(diǎn)類似 Django 的信號(hào)系統(tǒng)。比如有的同學(xué)可能知道 post_save ,我們可以用它來讓一個(gè)模型保存完畢之后做些事情。而組件則有許多這樣的東西。
Vue 給我們提供了組件在不同階段的接口。

關(guān)于生命周期更詳細(xì)的細(xì)節(jié),我們會(huì)在后面的章節(jié)里討論。

到這里我們就完成了這次重構(gòu)。趕緊試試效果吧。


本章我們初次接觸了 DRF 和 Vue ,并且重構(gòu)了一下試了試效果。DRF 則節(jié)約了我們不少接口開發(fā)的時(shí)間。vue 使我們的開發(fā)更加有調(diào)理,頁面不再是一團(tuán)亂麻。在下一章,我們將學(xué)習(xí)前端工具鏈。要一路從 node 學(xué)到 webpack 。

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

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

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