web開(kāi)發(fā)的兩種模式
1. 前后端不分離
完整的html頁(yè)面是在后端生成的,后端給前端返回完整的頁(yè)面,前端只是進(jìn)行展示,前端與后端的耦合度很高。
缺點(diǎn):只適用于純網(wǎng)頁(yè)的應(yīng)用。
優(yōu)點(diǎn):有利于網(wǎng)站的SEO優(yōu)化
2. 前后端分離
完整的html頁(yè)面是在前端生成的,后端只給前端返回所需的數(shù)據(jù),前端將數(shù)據(jù)填充在頁(yè)面上,前端與后端的耦合度很低。
優(yōu)點(diǎn):可以對(duì)接不同類型的客戶端。
缺點(diǎn):不利于SEO優(yōu)化
3.API: 在前后端分離開(kāi)發(fā)模式中,我們通常將后端開(kāi)發(fā)的每個(gè)視圖都稱為一個(gè)接口或者API。
4.RESTful設(shè)計(jì)風(fēng)格
統(tǒng)一的接口設(shè)計(jì)方式就是普遍采用的RESTful API設(shè)計(jì)風(fēng)格
Restful風(fēng)格設(shè)計(jì)-關(guān)鍵點(diǎn)
?limit=10:指定返回記錄的數(shù)量
?offset=10:指定返回記錄的開(kāi)始位置。
?page=2&pagesize=100:指定第幾頁(yè),以及每頁(yè)的記錄數(shù)。
?sortby=name&order=asc:指定返回結(jié)果按照哪個(gè)屬性排序,以及排序順序。
1. 獲取一組數(shù)據(jù),返回一組數(shù)據(jù)
2. 獲取指定數(shù)據(jù),返回指定數(shù)據(jù)
3. 新增數(shù)據(jù),返回新增的數(shù)據(jù)
4. 修改數(shù)據(jù),返回修改的數(shù)據(jù)
5. 刪除數(shù)據(jù),返回空
200 OK - [GET/PUT]:服務(wù)器成功返回用戶請(qǐng)求的數(shù)據(jù)
201 CREATED - [POST]:用戶新建數(shù)據(jù)成功。
204 NO CONTENT - [DELETE]:用戶刪除數(shù)據(jù)成功。
400 INVALID REQUEST - [POST/PUT]:用戶發(fā)出的請(qǐng)求有錯(cuò)誤,服務(wù)器沒(méi)有進(jìn)行新建或修改數(shù)據(jù)的操作
404 NOT FOUND - [*]:用戶發(fā)出的請(qǐng)求針對(duì)的是不存在的記錄,服務(wù)器沒(méi)有進(jìn)行操作,該操作是冪等的。。
500 INTERNAL SERVER ERROR - [*]:服務(wù)器發(fā)生錯(cuò)誤,用戶將無(wú)法判斷發(fā)出的請(qǐng)求是否成功。
了解:
需求:
設(shè)計(jì)一套符合RestAPI風(fēng)格的接口,提供以下5個(gè)接口:
1. 獲取所有直播間數(shù)據(jù):GET /lives/
2. 新增一本直播間數(shù)據(jù):POST /lives/
3. 獲取指定的直播間數(shù)據(jù)(根據(jù)id):GET /lives/(?P<pk>\d+)/
4. 修改指定的直播間數(shù)據(jù)(根據(jù)id):PUT /lives/(?P<pk>\d+)/
5. 刪除指定的直播間數(shù)據(jù)(根據(jù)id):DELETE /lives/(?P<pk>\d+)/
模型類定義
# models.py
class LiveInfo(models.Model):
live_id=models.IntegerField(verbose_name='直播間號(hào)')
live_streamer=models.CharField(max_length=20, verbose_name='主播名字')
live_title=models.CharField(max_length=30, verbose_name='直播間標(biāo)題')
live_pop=models.IntegerField(default=0, verbose_name='人氣')
live_content=models.CharField(default='未設(shè)定', max_length=20, verbose_name='直播類型')
is_delete=models.BooleanField(default=False, verbose_name='刪除標(biāo)記')
class Meta:
db_table='tb_lives'
verbose_name='直播間'
verbose_name_plural=verbose_name
def __str__(self):
return self.live_streamer
views.py文件中定義視圖實(shí)現(xiàn)API接口:
class LiveListView(View):
def get(self, request):
"""獲取所有直播間"""
lives=LiveInfo.objects.all()
lives_list=[]
for live in lives:
lives_list.append({
'live_id': live.live_id,
'live_streamer': live.live_streamer,
'live_title': live.live_title,
'live_pop': live.live_pop,
'live_content': live.live_content
})
return JsonResponse(lives_list, safe=False)
def post(self, request):
"""新增直播間"""
pass
class LiveDetailView(View):
def get(self, request, pk):
"""查詢一間直播間信息"""
pass
def put(self, request, pk):
"""修改直播間信息"""
pass
def delete(self, request, pk):
"""刪除直播間"""
pass
分析上節(jié)直播間管理的5個(gè)API接口,可以發(fā)現(xiàn),在開(kāi)發(fā)REST API接口時(shí),視圖中做的最主要有三件事:
在以上操作中,涉及到兩個(gè)概念:序列化和反序列化。
序列化Serialization
將程序中的一個(gè)數(shù)據(jù)結(jié)構(gòu)類型轉(zhuǎn)換為其他格式(字典、JSON、XML等),例如將Django中的模型類對(duì)象轉(zhuǎn)換為字典或JSON字符串,這個(gè)轉(zhuǎn)換過(guò)程我們稱為序列化。
反序列化
將其他格式(字典、JSON、XML等)轉(zhuǎn)換為程序中的數(shù)據(jù),例如將JSON字符串或字典轉(zhuǎn)換保存為Django中的模型類對(duì)象,這個(gè)過(guò)程我們稱為反序列化。
在開(kāi)發(fā)REST API接口時(shí),我們?cè)?strong>視圖中在做的最核心的事是:
Django REST framework 框架是一個(gè)用于構(gòu)建Web API 的強(qiáng)大而又靈活的工具。通常簡(jiǎn)稱為DRF框架 或 REST framework。
作用:幫助我們大大提高REST API的開(kāi)發(fā)速度
核心功能:
環(huán)境安裝與使用:
pip install djangorestframework
# 注:DRF框架依賴于Django,需要先安裝Django環(huán)境,再安裝djangorestframework。
在Django項(xiàng)目中使用DRF框架進(jìn)行開(kāi)發(fā)時(shí),需要將rest_framework在INSTALLED_APPS中進(jìn)行注冊(cè):
INSTALLED_APPS=[
...
'rest_framework',
]
序列化器類定義
from rest_framework import serializers
class LiveInfoSerializer(serializers.Serializer):
id=serializers.IntegerField(label='ID', read_only=True)
live_id=serializers.IntegerField(label='直播間號(hào)')
live_streamer=serializers.CharField(max_length=20, label='主播名字', required=False)
live_title=serializers.CharField(max_length=30, label='直播間標(biāo)題')
live_pop=serializers.IntegerField(default=0, label='人氣', required=False)
live_content=serializers.CharField(default='未設(shè)定', max_length=20, label='直播類型', required=False)
is_delete=serializers.BooleanField(default=False, label='刪除標(biāo)記', required=False)
選項(xiàng)參數(shù):
序列化操作
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveDetailView(View):
def get(self, request, pk):
"""查詢一間直播間信息"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
# 創(chuàng)建序列化器對(duì)象
serializer=LiveInfoSerializer(live)
# 返回序列化之后的數(shù)據(jù)
return JsonResponse(serializer.data)
class LiveListView(View):
def get(self, request):
"""獲取所有直播間"""
lives=LiveInfo.objects.all()
# 創(chuàng)建序列化器對(duì)象
serialize# 獲取序列化之后的數(shù)據(jù)r=LiveInfoSerializer(lives, many=True)
return JsonResponse(serializer.data, safe=False)
如果在序列化對(duì)象數(shù)據(jù)時(shí),需要將其關(guān)聯(lián)的對(duì)象一并序列化,則定義序列化器類的字段時(shí),需要在定義對(duì)應(yīng)的關(guān)聯(lián)對(duì)象嵌套序列化字段,比如和直播分類關(guān)聯(lián)的直播間。對(duì)于嵌套關(guān)聯(lián)字段,可以采用以下3種方式進(jìn)行定義:
1.PrimaryKeyRelatedField :將關(guān)聯(lián)對(duì)象序列化為關(guān)聯(lián)對(duì)象的主鍵。
# 在LiveInfoSerializer中添加此字段
Ltype=serializers.PrimaryKeyRelatedField(label='直播類型', read_only=True)
或
Ltype=serializers.PrimaryKeyRelatedField(label='直播類型', queryset=LiveInfo.objects.all())
2.使用關(guān)聯(lián)對(duì)象的序列化器
Ltype=LiveInfoSerializer()
3.StringRelatedField:將關(guān)聯(lián)對(duì)象序列化為關(guān)聯(lián)對(duì)象模型類__str__方法的返回值。
Ltype=serializers.StringRelatedField(label='直播類型')
如果和一個(gè)對(duì)象關(guān)聯(lián)的對(duì)象有多個(gè),在序列化器類中定義嵌套序列化字段時(shí),需要多添加一個(gè)many=True參數(shù)。
反序列化操作
# 1. 創(chuàng)建序列化器對(duì)象
serializer=序列化器類(data=<待校驗(yàn)字典數(shù)據(jù)>)
# 2. 數(shù)據(jù)校驗(yàn):成功返回True,失敗返回False
serializer.is_valid()
# 3. 獲取校驗(yàn)成功之后的數(shù)據(jù)
serializer.validated_data
# 4. 如果校驗(yàn)失敗,獲取失敗的錯(cuò)誤提示信息
serializer.errors
調(diào)用is_valid時(shí),會(huì)根據(jù)對(duì)應(yīng)序列化類字段是否需要傳遞、字段類型以及一些選項(xiàng)參數(shù)對(duì)data中的數(shù)據(jù)進(jìn)行校驗(yàn)。
在調(diào)用is_valid進(jìn)行數(shù)據(jù)校驗(yàn)時(shí),除了一些基本的默認(rèn)驗(yàn)證行為,可能還需要補(bǔ)充一些驗(yàn)證行為,可以使用以下三種方法:
數(shù)據(jù)校驗(yàn)通過(guò)之后,可以調(diào)用序列化對(duì)象的save方法進(jìn)行數(shù)據(jù)保存,save方法內(nèi)部會(huì)調(diào)用對(duì)應(yīng)序列化器類中的create或update方法,可以在create中實(shí)現(xiàn)數(shù)據(jù)新增,在update中實(shí)現(xiàn)數(shù)據(jù)更新。
ModelSerializer使用
如果序列化器類對(duì)應(yīng)的是Django的某個(gè)模型類,則定義序列化器類時(shí),可以直接繼承于ModelSerializer。
ModelSerializer是Serializer類的子類,相對(duì)于Serializer,提供了以下功能:
class LiveInfoSerializer(serializers.ModelSerializer):
class Meta:
model=LiveInfo
# 使用fields來(lái)指明依據(jù)模型類的哪些字段生成序列化器類的字段,__all__表明包含所有字段,也可以指明具體哪些字段
# fields='__all__'
# fields=('id', 'title', ...)
# 使用exclude可以指明排除哪些字段
exclude=['is_delete']
extra_kwargs={
# 'live_title': {'validators': [live_name]},
'live_streamer': {'required': False},
'live_pop': {'min_value': 0, 'required': False},
}
案例-序列化器改寫(xiě)后的視圖代碼:
import json
from django.http import JsonResponse, HttpResponse
from django.views import View
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(View):
def get(self, request):
"""獲取所有直播間"""
lives=LiveInfo.objects.all()
serializer=LiveInfoSerializer(lives, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
"""新增直播間"""
json_dict=json.dumps(request.body.decode())
serializer=LiveInfoSerializer(data=json_dict)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播間
return JsonResponse(serializer.data, status=201)
class LiveDetailView(View):
def get(self, request, pk):
"""查詢一間直播間信息"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
serializer=LiveInfoSerializer(live)
return JsonResponse(serializer.data)
def put(self, request, pk):
"""修改直播間信息"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
json_dict=json.dumps(request.body.decode())
serializer=LiveInfoSerializer(live, data=json_dict)
serializer.is_valid(raise_exception=True)
serializer.save()
return JsonResponse(serializer.data)
def delete(self, request, pk):
"""刪除直播間"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
return HttpResponse(status=404)
# 參數(shù)校驗(yàn)
live.delete()
return HttpResponse(status=204)
APIView
APIView是REST framework提供的所有視圖的基類,繼承自Django的View類。
APIView和View的區(qū)別:
案例-使用APIView改寫(xiě)RestAPI:
GenericAPIView
繼承自APIView,在APIView功能基礎(chǔ)上,主要增加了操作序列化器和數(shù)據(jù)庫(kù)查詢的屬性和方法。
GenericAPIView和APIView的區(qū)別:
案例-使用GenericAPIView改寫(xiě)RestAPI:
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(GenericAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
def get(self, request):
"""獲取所有直播間"""
queryset=self.get_queryset()
serializer=self.get_serializer(queryset, many=True)
return Response(serializer.data)
def post(self, request):
"""新增直播間"""
serializer=self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播間
return Response(serializer.data, status=status.HTTP_201_CREATED)
class LiveDetailView(GenericAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
def get(self, request, pk):
"""查詢一間直播間信息"""
instance=self.get_object()
serializer=self.get_serializer(instance)
return Response(serializer.data)
def put(self, request, pk):
"""修改直播間信息"""
instance=self.get_object()
serializer=self.get_serializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def delete(self, request, pk):
"""刪除直播間"""
instance=self.get_object()
# 參數(shù)校驗(yàn)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Mixin擴(kuò)展類
使用GenericAPIView改寫(xiě)之后的RestAPI接口中,圖書(shū)管理的各個(gè)代碼已經(jīng)變成了通用的代碼,這些代碼和視圖所使用的序列化器類和查詢集已經(jīng)沒(méi)有直接的關(guān)系,DRF框架已經(jīng)將這些代碼做了封裝,就是5個(gè)Mixin擴(kuò)展類。
案例-Mixin改寫(xiě)RestAPI接口:
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(mixins.ListModelMixin, mixins.CreateModelMixin,GenericAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
def get(self, request):
"""獲取所有直播間"""
return self.list(request)
def post(self, request):
"""新增直播間"""
return self.create(request)
class LiveDetailView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
def get(self, request, pk):
"""查詢一間直播間信息"""
return self.retrieve(request, pk)
def put(self, request, pk):
"""修改直播間信息"""
return self.update(request, pk)
def delete(self, request, pk):
"""刪除直播間"""
return self.destroy(request, pk)
子類視圖:Django框架為了方便視圖的編寫(xiě),還提供了9個(gè)子類視圖類。
子類視圖一定同時(shí)繼承了GenericAPIView和對(duì)應(yīng)的Mixin擴(kuò)展類,同時(shí)類中提供了對(duì)應(yīng)的請(qǐng)求處理方法,并且請(qǐng)求處理方法中調(diào)用的就是Mixin擴(kuò)展類中封裝的通用方法。
案例-子類視圖改寫(xiě)RestAPI接口:
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveListView(ListCreateAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
class LiveDetailView(RetrieveUpdateDestroyAPIView):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
視圖集ViewSet
將操作同一組資源相關(guān)的處理方法放在同一個(gè)類中,這個(gè)類叫做視圖集。
基本使用:
常用視圖集父類:
案例-ViewSet改寫(xiě):
from django.http import Http404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveInfoViewSet(ViewSet):
def list(self, request):
"""獲取所有直播間"""
lives=LiveInfo.objects.all()
serializer=LiveInfoSerializer(lives, many=True)
return Response(serializer.data)
def create(self, request):
"""新增直播間"""
serializer=LiveInfoSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# 返回新增直播間
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
"""查詢一間直播間信息"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
serializer=LiveInfoSerializer(live)
return Response(serializer.data)
def update(self, request, pk):
"""修改直播間信息"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
serializer=LiveInfoSerializer(live, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def destroy(self, request, pk):
"""刪除直播間"""
try:
live=LiveInfo.objects.get(pk=pk)
except LiveInfo.DoesNotExist:
raise Http404
# 參數(shù)校驗(yàn)
live.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py
from django.conf.urls import url
from booktest import views
urlpatterns=[
url(r'^lives/$', views.LiveInfoViewSet.as_view({
'get': 'list',
'post': 'create'
})),
url(r'^lives/(?P<pk>\d+)/$', views.LiveInfoViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
}))
]
案例-GenericViewSet改寫(xiě):
案例-ModelViewSet并添加了一些額外的方法改寫(xiě):
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from livetest.models import LiveInfo
from livetest.serializers import LiveInfoSerializer
class LiveInfoView(ModelViewSet):
serializer_class=LiveInfoSerializer
queryset=LiveInfo.objects.all()
lookup_value_regex='\d+'
@action(methods='get', detail=False)
def latest(self, request):
"""查詢最新的直播間"""
live=LiveInfo.objects.latest('pk')
serializer=self.get_serializer(live)
return Response(serializer.data)
@action(methods='put', detail=True)
def change_pop(self, request, pk):
"""修改指定直播間的人氣"""
live=self.get_object()
live.bread=request.data.get('live_pop')
live.save()
serializer=self.get_serializer(live)
return Response(serializer.data)
# urls.py
from django.conf.urls import url
from . import views
urlpatterns=[
url(r'^lives/$', views.LiveInfoViewSet.as_view({
'get': 'list',
'post': 'create'
})),
url(r'^lives/(?P<pk>\d+)/$', views.LiveInfoViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'delete': 'destroy'
})),
url(r'^lives/latest/$', views.LiveInfoViewSet.as_view({
'get': 'latest'
})),
url(r'^lives/(?P<pk>\d+)/change_pop/$', views.LiveInfoViewSet.as_view({
'put': 'change_pop'
}))
]
路由Router:動(dòng)態(tài)生成視圖集中處理方法的url配置項(xiàng)。
對(duì)于視圖集ViewSet,除了可以自己手動(dòng)進(jìn)行URL配置指明請(qǐng)求方式與action處理函數(shù)之間的對(duì)應(yīng)關(guān)系外,還可以使用路由Router來(lái)自動(dòng)生成路由信息。
REST framework提供了兩個(gè)Router類:
案例-router改寫(xiě):
# urls.py
from rest_framework.routers import DefaultRouter
from livetest import views
urlpatterns=[]
# 創(chuàng)建Router對(duì)象
router=DefaultRouter()
# 注冊(cè)router
router.register('lives', views.LiveInfoView, base_name='live')
# 添加配置項(xiàng)
urlpatterns +=router.urls
DRF框架到此常用的功能都已經(jīng)講解完畢了,當(dāng)然DRF框架還有其他功能:認(rèn)證、權(quán)限、限流、過(guò)濾、排序、分頁(yè)和異常處理機(jī)制。將在后面講述,敬請(qǐng)關(guān)注。
作者簡(jiǎn)介:Python菜鳥(niǎo)工程師,將在接下來(lái)的一段時(shí)間內(nèi)與大家分享一些與Python相關(guān)的知識(shí)點(diǎn)。如若文中出現(xiàn)問(wèn)題,各位大佬多多指點(diǎn),互相學(xué)習(xí)。喜歡的關(guān)注一個(gè)吧!謝謝!
Web是World Wide Web的簡(jiǎn)稱,中文譯為萬(wàn)維網(wǎng)
我們可以將它規(guī)劃成如下的幾個(gè)時(shí)代來(lái)進(jìn)行理解
石器時(shí)代指的就是我們的靜態(tài)網(wǎng)頁(yè),可以欣賞一下1997的Apple官網(wǎng)
最早的網(wǎng)頁(yè)是沒(méi)有數(shù)據(jù)庫(kù)的,可以理解成就是一張可以在網(wǎng)絡(luò)上瀏覽的報(bào)紙,直到CGI技術(shù)的出現(xiàn)
通過(guò) CGI Perl 運(yùn)行一小段代碼與數(shù)據(jù)庫(kù)或文件系統(tǒng)進(jìn)行交互,如當(dāng)時(shí)的Google(1998年)
ASP,JSP大家應(yīng)該都不會(huì)太陌生,最早出現(xiàn)于 2005 年左右,先后出現(xiàn)了微軟的 ASP 和 Java Server Pages [JSP] 等技術(shù),取代了 CGI ,增強(qiáng)了 WEB 與服務(wù)端的交互的安全性,類似于下面這樣,其實(shí)就是Java + HTML
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSP demo</title>
</head>
<body>
<img src="http://localhost:8080/web05_session/1.jpg" width=200 height=100 />
</body>
</html>
JSP有一個(gè)很大的缺點(diǎn),就是不太靈活,因?yàn)镴SP是在服務(wù)器端執(zhí)行的,通常返回該客戶端的就是一個(gè)HTML文本。我們每次的請(qǐng)求:獲取的數(shù)據(jù)、內(nèi)容的加載,都是服務(wù)器為我們返回染完成之后的 DOM,這也就使得我們開(kāi)發(fā)網(wǎng)站的靈活度大打折扣
在這種情況下,同年:Ajax火了(小細(xì)節(jié),這里為什么說(shuō)火了,因?yàn)?Ajax 技術(shù)并不是 2005 年出現(xiàn)的,他的雛形是 1999 年),現(xiàn)在看來(lái)很常見(jiàn)的技術(shù)手段,在當(dāng)時(shí)可是珍貴無(wú)比
到這里大家就更熟悉了,
移動(dòng)設(shè)備的普及,Jquery的出現(xiàn),以及SPA(Single Page Application 單頁(yè)面應(yīng)用)的雛形,Backbone EmberJS AngularJS 這樣一批前端框架隨之出現(xiàn),但當(dāng)時(shí)SPA的路不好走,例如SEO問(wèn)題,SPA 過(guò)多的頁(yè)面、復(fù)雜場(chǎng)景下 View 的綁定等,都沒(méi)有很好的處理
這幾年的飛速發(fā)展,節(jié)約了開(kāi)發(fā)人員大量的精力、降低了開(kāi)發(fā)者和開(kāi)發(fā)過(guò)程的門檻,極大提升了開(kāi)發(fā)效率和迭代速度,我們可以稱之其為工業(yè)時(shí)代
這里沒(méi)有文字,放一張圖感受一下
PS:這里為什么要說(shuō)這么多Web的歷史,我們可以看到Web技術(shù)的變化之大與快,每一種新的技術(shù)出現(xiàn)都是一些特定場(chǎng)景的解決方案,那我們今天的主角Vue又是為了解決什么呢?
我們接著往下看
Vue.js(/vju?/,或簡(jiǎn)稱為Vue)
是一個(gè)用于創(chuàng)建用戶界面的開(kāi)源JavaScript框架,也是一個(gè)創(chuàng)建單頁(yè)應(yīng)用的Web應(yīng)用框架。 2016年一項(xiàng)針對(duì)JavaScript的調(diào)查表明,Vue有著89%的開(kāi)發(fā)者滿意度。在GitHub上,該項(xiàng)目平均每天能收獲95顆星,為Github有史以來(lái)星標(biāo)數(shù)第3多的項(xiàng)目
同時(shí)也是一款流行的JavaScript前端框架,旨在更好地組織與簡(jiǎn)化Web開(kāi)發(fā)。Vue所關(guān)注的核心是MVC模式中的視圖層,同時(shí),它也能方便地獲取數(shù)據(jù)更新,并通過(guò)組件內(nèi)部特定的方法實(shí)現(xiàn)視圖與模型的交互
PS: Vue作者尤雨溪是在為AngularJS工作之后開(kāi)發(fā)出了這一框架。他聲稱自己的思路是提取Angular中為自己所喜歡的部分,構(gòu)建出一款相當(dāng)輕量的框架
最早發(fā)布于2014年2月
MVVM
表示的是 Model-View-ViewModel
這時(shí)候需要一張直觀的關(guān)系圖,如下
1.什么是組件化
一句話來(lái)說(shuō)就是把圖形、非圖形的各種邏輯均抽象為一個(gè)統(tǒng)一的概念(組件)來(lái)實(shí)現(xiàn)開(kāi)發(fā)的模式,在Vue
中每一個(gè).vue
文件都可以視為一個(gè)組件
2.組件化的優(yōu)勢(shì)
降低整個(gè)系統(tǒng)的耦合度,在保持接口不變的情況下,我們可以替換不同的組件快速完成需求,例如輸入框,可以替換為日歷、時(shí)間、范圍等組件作具體的實(shí)現(xiàn)
調(diào)試方便,由于整個(gè)系統(tǒng)是通過(guò)組件組合起來(lái)的,在出現(xiàn)問(wèn)題的時(shí)候,可以用排除法直接移除組件,或者根據(jù)報(bào)錯(cuò)的組件快速定位問(wèn)題,之所以能夠快速定位,是因?yàn)槊總€(gè)組件之間低耦合,職責(zé)單一,所以邏輯會(huì)比分析整個(gè)系統(tǒng)要簡(jiǎn)單
提高可維護(hù)性,由于每個(gè)組件的職責(zé)單一,并且組件在系統(tǒng)中是被復(fù)用的,所以對(duì)代碼進(jìn)行優(yōu)化可獲得系統(tǒng)的整體升級(jí)
解釋:指令 (Directives) 是帶有 v- 前綴的特殊屬性
作用:當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于 DOM
v-if
v-for
v-bind
v-on
v-model
沒(méi)有指令之前我們是怎么做的?是不是先要獲取到DOM然后在....干點(diǎn)啥
沒(méi)有落地使用場(chǎng)景的革命不是好革命,就以一個(gè)高頻的應(yīng)用場(chǎng)景來(lái)示意吧
注冊(cè)賬號(hào)這個(gè)需求大家應(yīng)該很熟悉了,如下
用jquery
來(lái)實(shí)現(xiàn)大概的思路就是選擇流程dom對(duì)象,點(diǎn)擊按鈕隱藏當(dāng)前活動(dòng)流程dom對(duì)象,顯示下一流程dom對(duì)象
如下圖(代碼就不上了,上了就篇文章就沒(méi)了..)
用vue
來(lái)實(shí)現(xiàn),我們知道vue
基本不操作dom
節(jié)點(diǎn), 雙向綁定使dom
節(jié)點(diǎn)跟視圖綁定后,通過(guò)修改變量的值控制dom
節(jié)點(diǎn)的各類屬性。
所以其實(shí)現(xiàn)思路為: 視圖層使用一變量控制dom節(jié)點(diǎn)顯示與否,點(diǎn)擊按鈕則改變?cè)撟兞?,如下圖
總結(jié)就是:
這里就做幾個(gè)簡(jiǎn)單的類比吧,當(dāng)然沒(méi)有好壞之分,只是使用場(chǎng)景不同
Vue
的weex
、React
的React native
。Vue
的vue-cli
、React
的Create React App
。數(shù)據(jù)變化的實(shí)現(xiàn)原理不同。react
使用的是不可變數(shù)據(jù),而Vue
使用的是可變的數(shù)據(jù)
組件化通信的不同。react
中我們通過(guò)使用回調(diào)函數(shù)來(lái)進(jìn)行通信的,而Vue
中子組件向父組件傳遞消息有兩種方式:事件和回調(diào)函數(shù)
diff算法不同。react
主要使用diff隊(duì)列保存需要更新哪些DOM,得到patch樹(shù),再統(tǒng)一操作批量更新DOM。Vue
使用雙向指針,邊對(duì)比,邊更新DOM。
@JS語(yǔ)音答題社群
1.初始設(shè)置
今天編寫(xiě)一個(gè)elm應(yīng)用實(shí)例。首先,做好初始設(shè)置。
Elm init
運(yùn)行命令后,會(huì)在目錄中創(chuàng)建一個(gè)elm.json配置文件,以及一個(gè)名為src的目錄,我們?cè)谠撃夸浿杏镁庉嬈?。可以是vscode、atom等專門甚至最簡(jiǎn)單的記事本。在src目錄中創(chuàng)建一個(gè)名為main.elm文件。該文件就是要編寫(xiě)應(yīng)用代碼的文件。
用編輯器打開(kāi)文件,建議用vscode,最世界上最流行的程序代碼編輯工具。
初次設(shè)置完成后,正式進(jìn)入elm編程之旅。
2.薛定諤的貓
估計(jì)許多人都聽(tīng)說(shuō)過(guò)薛定諤的貓的實(shí)驗(yàn)。在一個(gè)封閉的房間中放入一只活貓,房間中放置有毒藥陷阱。因?yàn)榉块g是密封的。在房門關(guān)閉時(shí),我們無(wú)法從外界觀測(cè)到貓是死是活,因此貓?zhí)幵谝环N非死非活的不確定性狀態(tài)中;當(dāng)我們打開(kāi)房門時(shí),可以觀察到貓的狀態(tài),貓的死活也確定下來(lái)。薛定諤的貓闡述的是一種量子不確定現(xiàn)象。
我們的任務(wù)就是編寫(xiě)模擬薛定諤的貓實(shí)驗(yàn)的elm應(yīng)用代碼。
3.定義規(guī)則
我們需要定義一個(gè)包含門、鎖、毒藥陷阱(拔掉保險(xiǎn)絲時(shí)開(kāi)始運(yùn)轉(zhuǎn))三種事物的房間。
規(guī)則1:如果門開(kāi),那么拔掉保險(xiǎn)絲。
規(guī)則2:如果保險(xiǎn)絲被拔掉,它能夠重新放上。
規(guī)則3:如果門開(kāi),重新放保險(xiǎn)絲。
規(guī)則4:如果門開(kāi),能關(guān)門。
規(guī)則5:如果門關(guān),能開(kāi)門或上鎖。
規(guī)則6:如果上鎖,能解鎖。
每一種事物都可以用多種可能的狀態(tài)組合表示,并且明確可能狀態(tài)之間的轉(zhuǎn)換規(guī)則是什么。
上述規(guī)矩轉(zhuǎn)成對(duì)象狀態(tài)如下:
-- 可能的狀態(tài):
Door(門):
Locked(上鎖)
Closed(關(guān)門)
Opened(開(kāi)門)
Alarm(毒藥陷阱):
Armed (觸發(fā))
Disarmed (沒(méi)觸發(fā))
Triggered(保險(xiǎn)絲)
--可能的狀態(tài)組合有:
Locked + Armed(上鎖+毒藥陷阱觸發(fā))
Locked + Triggered(上鎖+拔掉保險(xiǎn)絲)
Locked + Disarmed (上鎖+毒藥陷阱沒(méi)有觸發(fā))
Unlocked + Armed (開(kāi)鎖+毒藥陷阱觸發(fā))
Unlocked + Triggered (開(kāi)鎖+拔掉保險(xiǎn)絲)
Unlocked + Disarmed (開(kāi)鎖+毒藥陷阱沒(méi)有觸發(fā))
Opened + Triggered (開(kāi)門+拔掉保險(xiǎn)絲)
Opened + Disarmed(開(kāi)門+毒藥陷阱沒(méi)有觸發(fā))
--互相轉(zhuǎn)換的狀態(tài)有:
Door(門):
Closed <-> Locked (關(guān)門 <-> 上鎖)
Closed <-> Opened (關(guān)門 <-> 開(kāi)門)
Alarm(毒藥陷阱):
Armed -> Triggered (毒藥陷阱觸發(fā) -> 拔掉保險(xiǎn)絲)
Triggered -> Disarmed (拔掉保險(xiǎn)絲 -> 毒藥陷阱沒(méi)有觸發(fā))
Armed <-> Disarmed (毒藥陷阱觸發(fā) <-> 毒藥陷阱沒(méi)有觸發(fā))
4.建立模型
OK,規(guī)則確定好后,我們會(huì)發(fā)現(xiàn),在監(jiān)測(cè)開(kāi)始的時(shí)間點(diǎn),房間只能真正存在一種可能的狀態(tài)組合。因此要定義一個(gè)顯示房間狀態(tài)的模型model,它包括了監(jiān)測(cè)時(shí)間點(diǎn)時(shí)的狀態(tài)等。這個(gè)模型要將失敗的情景加上去,防止觀測(cè)不到的錯(cuò)誤產(chǎn)生,用自定義類型添加到mail.elm代碼中去。
? ? ? type Model
? ? ? ? ? ?=DisplayingRoom DoorState AlarmState
? ? ? ? ? ? | Failure String
?
? ? ? type DoorState
? ? ? ? ? ?=Opened
? ? ? ? ? ? | Closed
? ? ? ? ? ? | Locked
?
? ? ? type AlarmState
? ? ? ? ? ?=Armed
? ? ? ? ? ? | Disarmed
? ? ? ? ? ? | Triggered
好了,我們薛定諤的貓模型建立起來(lái),完成第一步代碼。
5.建立更新模型的邏輯
要對(duì)模型進(jìn)行更新,必須要有信息通知到達(dá)才能開(kāi)始更新。因此更新邏輯前需先定義消息:
? ? ? type Msg
? ? ? ? ? ?=Open
? ? ? ? ? ? | Close
? ? ? ? ? ? | Lock
? ? ? ? ? ? | Unlock
? ? ? ? ? ? | Arm
? ? ? ? ? ? | Disarm
消息定義后,開(kāi)始定義update更新函數(shù),實(shí)現(xiàn)消息->模型->返回一個(gè)新模型,我們先從檢查房間狀態(tài)開(kāi)始定義:
? ? ? update msg model=
? ? ? ? ? ? case model of
? ? ? ? ? ? ? ? ? DisplayingRoom doorState alarmState ->
? ? ? ? ? ? ? ? ? ? ? ? ...?
? ? ? ? ? ? Failure errorMessage ->
? ? ? ? ? ? ? ? ? ? ? ? model
先建立函數(shù)框架,如果因?yàn)楣收显驘o(wú)法觀測(cè)到房間狀態(tài),那么更新函數(shù)只能返回原來(lái)的模型model。
當(dāng)可以正常地觀測(cè)房間時(shí),如果房門處于打開(kāi)狀態(tài)下,受到的限制最大:門開(kāi)、鎖解、毒藥陷阱不能起作用。
第一步,讓我們添加房門打開(kāi)時(shí)的更新代碼,擴(kuò)展原來(lái)基礎(chǔ)框架:
? ? ? update : Msg -> Model -> Model
? ? ? update msg model?=
? ? ? ? ? ? case model of
? ? ? ? ? ? ? ? ? DisplayingRoom doorState alarmState ->
? ? ? ? ? ? ? ? ? ? ? ? case doorState of
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Opened ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? case msg of
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Close ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Closed alarmState
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _ -> Failure "故障,觀測(cè)不到!”?
? ? ? ? ? ? ? ? ? Failure _ ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? model
直到門關(guān),毒藥陷阱才可能被觸發(fā)。
上述代碼中,我們處理了,房間由開(kāi)門轉(zhuǎn)為關(guān)門時(shí)模型的更新代碼:
? ? ? ? ? ? ? ? ? DisplayingRoom Closed alarmState
其中alarmState是包含了毒藥陷阱觸發(fā)或沒(méi)有被觸發(fā)兩種狀態(tài)的變量,確保門由開(kāi)轉(zhuǎn)關(guān)時(shí)。毒藥陷阱的狀態(tài)可以原封不動(dòng)地轉(zhuǎn)移到新的模型上。
? ? ? ? ? ? ? ? ? _ -> Failure "故障,觀測(cè)不到!"
這是一個(gè)兜底代碼,確保無(wú)法獲取監(jiān)測(cè)狀態(tài)時(shí)返回一個(gè)消息通知。這種考慮周全的機(jī)制,也是elm編譯運(yùn)行不出錯(cuò)的優(yōu)勢(shì)。
第二步,讓我們添加門關(guān)時(shí)的更新邏輯,較為復(fù)雜,打開(kāi)門時(shí),里面涉及到鎖的狀態(tài)、毒藥陷阱的狀態(tài)。
在門關(guān)的前提下,如果收到開(kāi)門的消息,那么要檢測(cè)毒藥陷阱的狀態(tài),觸發(fā)或沒(méi)有觸發(fā)。因?yàn)槎舅幭葳宓臓顟B(tài),關(guān)系到貓的死活。如果毒藥陷阱一直不被觸發(fā),貓仍然存活。但這些在未打開(kāi)房門的情況下,我們是無(wú)法監(jiān)測(cè)而至的,現(xiàn)在添加門關(guān)情況下的代碼。
? ? ? Closed ->
? ? ? ? ? ? case?msg of
? ? ? ? ? ? ? ? ? Open ->
? ? ? ? ? ? ? ? ? ? ? ? case alarmState of
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Armed ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Opened Triggered
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _ ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Opened alarmState
???????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Lock ->
?????????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Locked alarmState
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Arm ?->
???????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Closed Armed
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Disarm ->
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?DisplayingRoom Closed Disarmed
? ? ? ? ? ? _ -> Failure "故障,觀測(cè)不到!"
第三步,讓我們添加門鎖住情況下的更新代碼:
? ? ? Locked ->
? ? ? ? ? ? case msg of
? ? ? ? ? ? ? ? ? Unlock ->
? ? ? ? ? ? ? ? ? ? ? ? DisplayingRoom Closed alarmState
? ? ? ? ? ? ? ? ? Arm -> DisplayingRoom Locked Armed
? ? ? ? ? ? ? ? ? ? ? ? Disarm -> DisplayingRoom Locked Disarmed
? ? ? ? ? ? ? ? ? _ -> Failure "故障,觀測(cè)不到!"
好了,我們代碼中最重要的引擎,update更新函數(shù)完成了。下一步是把更新后的模型通過(guò)UI視圖顯示出來(lái),這對(duì)于elm來(lái)說(shuō)輕而易舉,因?yàn)樗旧砭褪菫榱藰?gòu)建前端而生。
6.構(gòu)建前端界面
elm有一個(gè)HTML庫(kù),它使我們可以在elm中編寫(xiě)HTML代碼:
? ? ? import Html exposing (..)
現(xiàn)在通過(guò)來(lái)引入這個(gè)庫(kù),為簡(jiǎn)單起見(jiàn),這里只是把房間的狀態(tài)用文字在html界面中展示出來(lái):
首先建立一個(gè)failure情況下的顯示函數(shù):
? ? ? failure message=
? ? ? ? ? ? div []
? ? ? ? ? ? ? ? ? [ p [] [ "故障,觀測(cè)不到!" ] ]
然后把各種狀態(tài)組合用文字展示出來(lái),這里要注意的是,按照房間規(guī)則,門關(guān)時(shí)的狀態(tài)要有兩個(gè)可能的消息,但是門開(kāi)時(shí)只需要一條消息即可。最后UI界面代碼如下:
? ? ? View: Model -> Html Msg
? ? ? view model=
? ? ? ? ? ? Case model of
???????? ? ? ? ? ? Failure message=
? ? ? ? ? ? ? ? ? ? ? ? div []
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? p [] [ "故障,觀測(cè)不到!" ] ]
?????????
???????? ? ? ? ? ? DispalyingRoom doorState alarmState?=
? ? ? ? ? ? ? ? ? ? ? ? div []
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [case doorState of
???????????????????????? ? ? ? ? ? Opened ->
??????????????????????????? ? ? ? ? ? Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "門開(kāi)?-> 關(guān)門!" ] ]
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Closed=
??????????????????????????? ? ? ? ? ? Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "門關(guān)?-> 開(kāi)門!" ] ]
???????????????????????? ? ? ? ? ? Locked=
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "門鎖?-> 開(kāi)鎖!" ] ]
?????????????????????? ? ? ? ? ? ]
?????????????? ? ? ?,div []
????????????????? ? ? ? ? ? [ case alarmState of
???????????????????????? ? ? ? ? ? Armed ->
??????????????????????????? ? ? ? ? ? Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "毒藥陷阱被觸發(fā)?-> 開(kāi)門!" ] ]
???????????????????????? ? ? ? ? ? disArmed ->
??????????????????????????? ? ? ? ? ? Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "毒藥陷阱沒(méi)有觸發(fā)?-> 毒藥陷阱被觸發(fā)!" ] ]
???????????????????????? ? ? ? ? ? Triggered ->
??????????????????????????? ? ? ? ? ? Div []
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [ p [] [ "拔掉保險(xiǎn)絲?-> 毒藥陷阱被觸發(fā)/毒藥陷阱沒(méi)有觸發(fā)!" ] ]
????????????????? ? ? ? ? ? ? ? ? ]
為簡(jiǎn)單起見(jiàn),這里只用文字顯示作為HTML的內(nèi)容,但elm的HTML庫(kù)還有許多強(qiáng)大的功能,可以與react的JSX比美。下回我們?cè)賴L試構(gòu)建更絢麗的圖象或動(dòng)畫(huà)顯示房間狀態(tài)。
7.連接代碼
模型、更新、界面代碼已經(jīng)寫(xiě)好,剩下的是把這幾部分連接起來(lái)。我們需要ELM的主核心Browser模塊里面的沙箱sandbox:
? ? ? Import Browser exposing (..)
沙箱允許您創(chuàng)建使用elm架構(gòu)的應(yīng)用程序,但不會(huì)與”外部世界”對(duì)話(即任何外部的API或JavaScript,如果需要與外部世界對(duì)話可以用Browser.element或其它)。建立沙箱前我們還要聲明一下房間的初始狀態(tài),假說(shuō)為門已經(jīng)關(guān)閉、貓已經(jīng)放入、毒藥陷阱的保險(xiǎn)絲已經(jīng)拔開(kāi)。
? ? ? initialModel : Model
? ? ? initialModel=DisplayingRoom Closed Armed
? ? ? main : Program () Model Msg
? ? ? ? ? ? main=Browser.sandbox
? ? ? ? ? ? ? ? ? { init=initialModel
? ? ? ? ? ? ? ? ? , view=view
? ? ? ? ? ? ? ? ? , update=update
? ? ? ? ? ? ? ? ? }
8.運(yùn)行程序
在終端中運(yùn)行命令:
? ? ? Elm make src/main.elm
Elm make 是elm的編譯器命令,它把main.elm編譯成一個(gè)html文件。可以用瀏覽器打個(gè)這個(gè)html文件,讓我們作為觀察者,通過(guò)開(kāi)門、關(guān)門的點(diǎn)擊操作來(lái)模擬這個(gè)薛定諤的貓的實(shí)驗(yàn)。
(備注:本周寫(xiě)的文章學(xué)習(xí)了尼莫的《我希望有的榆木示例》思路,三體狀態(tài)不容易描述,用javascript寫(xiě)估計(jì)一大堆代碼,elm容易讀些,薛定諤的貓實(shí)驗(yàn)因?yàn)樯婕暗讲淮_定性,應(yīng)該還要引入隨機(jī)發(fā)生器,有時(shí)間再慢慢改進(jìn)。)
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。