從前端踏入後端
先交代一下心路歷程,佔個版面
去年三月開始進公司寫前端
-
html, css, javascript
-
react, redux
-
restful, graphQL
lodash, moment, third party library
other knowledge......
中間花了點時間參與糞 game
突然來了個訊息
今年8月開始寫後端 + typescript
mysql
apollo-server
sequelize, typeorm
koa, express
nginx, docker, gcp
linux
讓後端大概知道前端在想什麼
前端大概知道後端在想什麼
今日主題
以及我當中的開發心得 ><
Application
完成功能且讓使用者愉悅
UI/UX + front-end + back-end
使用畫面/流程/體驗
在對的時間點呈現合邏輯的畫面
功能正確性
PM?
UI/UX + front-end + back-end
使用畫面/流程/體驗
front-end + back-end
使用體驗
差的體驗?研訓院
- 等待時間過長 (資訊載入/寫入速度)
- 狀態置入 (initialValue/initialState)
舉個例子
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
front-end
state
back-end
request
response
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
apollo client cahce
redux
狀態
state
routing
auth
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
狀態
auth
singleton
single source of truth
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
發 request
注意 cache 和畫面是否同步
local cache 一致性
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
- 注意時間複雜度
- 熟悉 data structure
- lodash 好幫手
重組資料
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
重組資料
- 注意時間複雜度
- 熟悉 data structure
- lodash 好幫手
可讀性 > 時間複雜度
寫一個很屌的 reduce,
但可能你過了一個月後就要花更多時間去看懂它
=> 去權衡增加時間複雜度換個可讀性更好的 function
reduce 通常可以取代 map + filter
groupBy/OrderBy
Map, WeakMap
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (前端視角)
正確的使用 framework 裡的 method
所達成的優化更加明顯
useEffect/useMemo
render/更新狀態
注意重新 render 的次數
useMemo 盡量不要存 DOM,
應該是存複雜運算的結果
前端
完成功能的前提下,
在對的時間點呈現合邏輯的畫面
啊優化就大概兩個面向
降低 render 所需時間,減少 bundleSize
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (後端視角)
function Component(props): JSX.Element {
const { id } = useParams();
const { state, setState } = useState();
const { dataA } = useQuery(A)
const { dataB } = useQuery(B)
const aggregateData = useMemo(() => {
/** 複雜運算 */
}, [dataA, dataB, state]);
return (
<div>
...
</div>
)
}
發 request
restful
graphql
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (後端視角)
front-end
state
back-end
狀態 -> 發 request -> 重組資料 -> render/更新狀態
call API (後端視角)
front-end
state
back-end
Web server
database
back-end
Web server
database
SQL
SELECT column-names
FROM table-name
WHERE condition
(ORM)
ORM 做了啥?
先看看 database 在做什麼
=> 資料該怎麼存
規劃 table 和 column 以及對應關聯
- 建立Model 和 Table 去對應
2. 寫 resolver 時去實作 model 進行 CRUD, 並組資料
而 ORM 做了什麼
=> 中間人去做 CRUD
常見的後端日常
- 建立Model 和 Table 去對應
2. 寫 resolver 時去實作 model 進行 CRUD, 並組資料
@Entity({ name: 'Activities' })
export class Activity extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({
type: 'enum',
enum: ActivityType,
default: ActivityType.SIGN_UP,
})
type: ActivityType;
@OneToOne(() => SignUpActivity, (signUpActivity) => signUpActivity.activity)
signUpActivity: SignUpActivity;
@OneToOne(
() => AnnouncementActivity,
(announcementActivity) => announcementActivity.activity,
)
announcementActivity: AnnouncementActivity;
@OneToOne(() => InformationActivity, (informationActivity) => informationActivity.activity)
informationActivity: InformationActivity;
@ManyToMany(() => Hashtag)
@JoinTable({
name: 'ActivityHashtags',
joinColumn: {
name: 'ActivityId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'HashtagId',
referencedColumnName: 'name',
},
})
hashtags: Hashtag[];
}
- 建立Model 和 Table 去對應
2. 寫 resolver 時去實作 model 進行 CRUD, 並組資料
@Resolver(() => ActivityInterface)
export class ActivityResolver {
@Query(() => [ActivityInterface])
async activities(
@Arg('type', () => ActivityType, { nullable: true }) type: ActivityType,
@Arg('category', () => ActivityCategory, { nullable: true }) category: ActivityCategory,
@Arg('searchTerm', { nullable: true }) searchTerm: String,
@Args() {
offset, limit,
}: OffsetLimitArg,
) {
const qb = getRepository(Activity)
.createQueryBuilder('activity')
.leftJoinAndSelect('activity.signUpActivity', 'signUpActivity')
.offset(offset)
.limit(limit)
.where('activity.releasedAt <= CURRENT_TIMESTAMP')
.orderBy('activity.releasedAt', 'DESC');
if (type !== undefined) qb.andWhere('activity.type =:type', { type });
if (category !== undefined) qb.andWhere('activity.category =:category', { category });
if (searchTerm) {
qb.andWhere(
new Brackets(
(queryBuilder) => queryBuilder
.orWhere('activity.title LIKE :like')
.orWhere('EXISTS (SELECT * FROM ActivityHashtags WHERE HashtagId LIKE :like AND ActivityId = activity.id)'),
),
{ like: `%${searchTerm.replace('%', '\\%').replace('_', '\\_')}%` },
);
qb.andWhere('activity.type !=:type', { type: 'INFORMATION' });
}
return qb.getMany();
}
}
那什麼時候要決定多開什麼 API ?
所以當有 order, filter 時的條件,就是需要後端開 API 的時候
要先知道拉多筆資料時要設定 offset, limit,
才不會拉整個 table
好
剛剛看完是前端和後端喬 API 的溝通
讓我們拉回前端
前端從頭開發大概是怎麼回事
前端架構指引 ->
要做哪些共用 UI 元件, 頁面對應層級
瀏覽過一遍設計圖
釐清需求,瀏覽過一遍 wireframe
要採用哪些技術(框架以及其生態系套件)
後端 開發
-> API 設計
-> 要注意會不會拉太多資料以及 table
-> transaction
瀏覽過一遍設計圖
釐清需求,瀏覽過一遍 wireframe
要採用哪些技術(框架以及其生態系套件)
建 table, 確認每個 table 的關聯性
CPU bound
I/O
bound
我們一樣從輸入網址列開始講起 XD
阿還有沒有其他優化系統的方向
有
browser
wifi
router
DNS
server
host(url) -> IP
DNS不是url 換IP
是 host
url 跟 host 不一樣
url包含 協定, port, path...
DNS不會知道那些
TCP, TLS
client
AP server
AP server
AP server
AP server
單機
nginx
rytass.com
rytass.admin.com
1.2.3.4:6666
1.2.3.4:8888
reverse proxy
client
load balance
AP server
AP server
AP server
AP server
分散式
client
AP server
database
CDN
Redis
cache
redis -> 記憶體
database -> 硬碟
全端 開發
client
isomorphic
server
enum, error
official
admin
UI, asset
model
Text
official
admin
Thanks
Reference
- 一個好的系統攻略本
- 成為看起來很強的後端
- bob
從前端踏入後端
By Jay Chou
從前端踏入後端
- 712