Immutable.js 기반 리 셋 취소 기능 의 인 스 턴 스 코드
11420 단어 Immutable.js취소 하 다.
redux-undo
을 도입 한 후에 모든 작업 이'취소 가능 한'것 으로 바 뀌 었 습 니 다.그리고 우 리 는 그 설정 을 계속 수정 하여 취소 기능 을 점점 더 잘 사용 하도록 합 니 다(이것 도redux-undo 에 그렇게 많은 설정 항목 이 있 는 이유.본 고 는 아래 에서 위로 향 하 는 사 고 를 이용 하여 간단 하고 간단 한 온라인 그림 그리 기 도 구 를 예 로 들 어TypeScript,Immutable.js을 사용 하여 실 용적 인'취소 재 작업'기능 을 실현 할 것 이다.대략적인 효 과 는 다음 그림 과 같다.
첫 번 째 단계:과거 기록 이 필요 한 상 태 를 확인 하고 사용자 정의 State 클래스 를 만 듭 니 다.
모든 상태 에 역사적 기록 이 필요 한 것 은 아니다.많은 상 태 는 매우 자질구레 하 다.특히 마우스 나 키보드 와 상호작용 하 는 상태 이다.예 를 들 어 그림 그리 기 도구 에서 그림 을 끌 때 우 리 는'드래그 하고 있다'는 표 시 를 설정 해 야 한다.페이지 는 이 태그 에 따라 해당 하 는 드래그 알림 을 표시 하고 드래그 표 시 는 역사 기록 에 나타 나 지 말 아야 한 다 는 것 을 나타 낸다.다른 상 태 는 취소 되 지 않 거나 취소 되 지 않 아 도 됩 니 다.예 를 들 어 웹 창 크기,배경 으로 보 낸 요청 목록 등 입 니 다.
과거 기록 이 필요 없 는 상 태 를 제외 하고 남 은 상 태 를 Immutable Record 로 밀봉 하고 State 클래스 를 정의 합 니 다.
// State.ts
import { Record, List, Set } from 'immutable'
const StateRecord = Record({
items: List<Item>
transform: d3.ZoomTransform
selection: number
})
// , TypeScript, Immutable 4.0
export default class State extends StateRecord {}
여기 서 우리 의 예 는 간단 한 온라인 그래 픽 도구 이기 때문에 위의 State 클래스 에는 세 개의 필드 가 포함 되 어 있 습 니 다.items 는 이미 그 려 진 도형 을 기록 하고 transform 은 화판 의 이동 과 크기 조정 상 태 를 기록 하 며 selection 은 현재 선택 한 도형 의 ID 를 표시 합 니 다.그림 그리 기 도구 의 다른 상태,예 를 들 어 그림 그리 기 미리 보기,자동 정렬 설정,동작 알림 텍스트 등 은 State 클래스 에 두 지 않 았 습 니 다.두 번 째 단계:Action 기본 클래스 를 정의 하고 각 작업 에 대응 하 는 Action 하위 클래스 를 만 듭 니 다.
redux-undo 와 달리 우 리 는 명령 모드 를 사용 합 니 다.기본 액 션 을 정의 하고 State 에 대한 모든 작업 은 Action 의 인 스 턴 스 로 봉 인 됩 니 다.몇몇 Action 의 하위 클래스 를 정의 하고 서로 다른 유형의 작업 에 대응 합 니 다.
TypeScript 에서 Action 기본 클래스 는 Abstract Class 로 정의 하 는 것 이 편리 합 니 다.
// actions/index.ts
export default abstract class Action {
abstract next(state: State): State
abstract prev(state: State): State
prepare(appHistory: AppHistory): AppHistory { return appHistory }
getMessage() { return this.constructor.name }
}
Action 대상 의 next 방법 은'다음 상태'를 계산 하고 prev 방법 은'이전 상태'를 계산 합 니 다.getMessage 방법 은 Action 대상 의 짧 은 설명 을 가 져 오 는 데 사 용 됩 니 다.getMessage 방법 을 통 해 저 희 는 사용자 의 조작 기록 을 페이지 에 표시 하여 최근 에 무슨 일이 일 어 났 는 지 더욱 편리 하 게 알 수 있 습 니 다.prepare 방법 은 Action 이 처음 응용 되 기 전에'준비'를 하 는 데 사 용 됩 니 다.AppHistory 의 정 의 는 본 논문 뒤에 제 시 됩 니 다.Action 하위 클래스 예
아래 의 AddItemAction 은 전형 적 인 Action 하위 클래스 로'새로운 도형 추가'를 표현 하 는 데 사 용 됩 니 다.
// actions/AddItemAction.ts
export default class AddItemAction extends Action {
newItem: Item
prevSelection: number
constructor(newItem: Item) {
super()
this.newItem = newItem
}
prepare(history: AppHistory) {
// , state.selection
// prepare 「 selection 」 this.prevSelection
this.prevSelection = history.state.selection
return history
}
next(state: State) {
return state
.setIn(['items', this.newItem.id], this.newItem)
.set('selection', this.newItemId)
}
prev(state: State) {
return state
.deleteIn(['items', this.newItem.id])
.set('selection', this.prevSelection)
}
getMessage() { return `Add item ${this.newItem.id}` }
}
실행 시 행동실행 할 때 사용자 가 상호작용 을 하면 Action 흐름 이 생 깁 니 다.Action 대상 이 생 길 때마다 저 희 는 이 대상 의 next 방법 으로 다음 상 태 를 계산 한 다음 에 이 action 을 목록 에 저장 하여 사용 합 니 다.사용자 가 취소 작업 을 진행 할 때,우 리 는 action 목록 에서 최근 action 을 꺼 내 서 prev 방법 을 호출 합 니 다.실행 할 때 next/prev 방법 이 호출 된 상황 은 다음 과 같 습 니 다.
// initState
// , action1 ...
state1 = action1.next(initState)
// , action2 ...
state2 = action2.next(state1)
// ,action3 ...
state3 = action3.next(state2)
// , action prev
state4 = action3.prev(state3)
// , action action, prev
state5 = action2.prev(state4)
// , action, next
state6 = action2.next(state5)
Applied-Action
뒤의 설명 을 편리 하 게 하기 위해 우 리 는 Applied-Action 에 대해 간단 한 정 의 를 내 렸 다.Applied-Action 은 조작 결과 가 현재 응용 상태 에 반 영 된 action 을 말한다.action 의 next 방법 이 실 행 될 때 이 action 은 applied 로 변 합 니 다.prev 방법 이 실 행 될 때 이 action 은 unapplied 로 변 합 니 다.세 번 째 단계:과거 기록 용기 만 들 기 AppHistory
앞의 State 클래스 는 특정한 시간 에 응 용 된 상 태 를 나타 내 는 데 사 용 됩 니 다.다음은 AppHistory 클래스 가 응 용 된 역사 기록 을 나타 내 는 데 사 용 됩 니 다.마찬가지 로 우 리 는 여전히 Immutable Record 를 사용 하여 역사 기록 을 정의 합 니 다.그 중에서 state 필드 는 현재 응용 상 태 를 표현 하고 list 필드 는 모든 action 을 저장 하 며 index 필드 는 최근 applied-action 의 아래 표 시 를 기록 합 니 다.응 용 된 역사 상 태 는 undo/redo 방법 으로 계산 할 수 있 습 니 다.apply 방법 은 AppHistory 에 구체 적 인 Action 을 추가 하고 실행 하 는 데 사 용 됩 니 다.구체 적 인 코드 는 다음 과 같다.
// AppHistory.ts
const emptyAction = Symbol('empty-action')
export const undo = Symbol('undo')
export type undo = typeof undo // TypeScript2.7 symbol
export const redo = Symbol('redo')
export type redo = typeof redo
const AppHistoryRecord = Record({
//
state: new State(),
// action
list: List<Action>(),
// index applied-action list 。-1 applied-action
index: -1,
})
export default class AppHistory extends AppHistoryRecord {
pop() { //
return this
.update('list', list => list.splice(this.index, 1))
.update('index', x => x - 1)
}
getLastAction() { return this.index === -1 ? emptyAction : this.list.get(this.index) }
getNextAction() { return this.list.get(this.index + 1, emptyAction) }
apply(action: Action) {
if (action === emptyAction) return this
return this.merge({
list: this.list.setSize(this.index + 1).push(action),
index: this.index + 1,
state: action.next(this.state),
})
}
redo() {
const action = this.getNextAction()
if (action === emptyAction) return this
return this.merge({
list: this.list,
index: this.index + 1,
state: action.next(this.state),
})
}
undo() {
const action = this.getLastAction()
if (action === emptyAction) return this
return this.merge({
list: this.list,
index: this.index - 1,
state: action.prev(this.state),
})
}
}
STEP 4:'다시 시작 취소'기능 추가만약 에 응용 중의 다른 코드 가 웹 페이지 의 상호작용 을 일련의 Action 대상 으로 바 꾸 었 다 고 가정 하면 응용 에'다시 시작 취소'기능 을 추가 하 는 대체적인 코드 는 다음 과 같다.
type HybridAction = undo | redo | Action
// Redux , reudcer 「 」
// reducer
function reducer(history: AppHistory, action: HybridAction): AppHistory {
if (action === undo) {
return history.undo()
} else if (action === redo) {
return history.redo()
} else { // Action
// prepare , action「 」
return action.prepare(history).apply(action)
}
}
// Stream/Observable , reducer
const action$: Stream<HybridAction> = generatedFromUserInteraction
const appHistory$: Stream<AppHistory> = action$.fold(reducer, new AppHistory())
const state$ = appHistory$.map(h => h.state)
// , reducer
onActionHappen = function (action: HybridAction) {
const nextHistory = reducer(getLastHistory(), action)
updateAppHistory(nextHistory)
updateState(nextHistory.state)
}
다섯 번 째 단계:Action 을 합병 하여 사용자 의 상호작용 체험 을 보완 한다.위의 네 가지 절 차 를 통 해 그림 그리 기 도 구 는 취소 재 작업 기능 을 가지 지만 이 기능 은 사용자 체험 이 좋 지 않다.그림 그리 기 도구 에서 그림 을 끌 때 MoveItemAction 의 발생 빈 도 는 mousemove 사건 의 발생 빈도 와 같 습 니 다.만약 에 우리 가 이 상황 을 처리 하지 않 으 면 MoveItemAction 은 전체 역사 기록 을 오염 시 킬 것 입 니 다.우 리 는 기 록 된 모든 액 션 이 합 리 적 으로 입 도 를 취소 할 수 있 도록 주파수 가 높 은 액 션 을 통합 해 야 한다.
모든 Action 이 적용 되 기 전에 prepare 방법 이 호출 됩 니 다.prepare 방법 에서 역사 기록 을 수정 할 수 있 습 니 다.예 를 들 어 MoveItemAction 에 대해 저 희 는 이전 action 이 현재 action 과 같은 이동 작업 에 속 하 는 지 판단 한 다음 에 현재 action 을 응용 하기 전에 이전 action 을 제거 할 지 여 부 를 결정 합 니 다.코드 는 다음 과 같 습 니 다:
// actions/MoveItemAction.ts
export default class MoveItemAction extends Action {
prevItem: Item
// :
// (startPos), (movingPos), ID
constructor(readonly startPos: Point, readonly movingPos: Point, readonly itemId: number) {
// readonly startPos: Point :
// 1. MoveItemAction startPos
// 2. this.startPos = startPos
super()
}
prepare(history: AppHistory) {
const lastAction = history.getLastAction()
if (lastAction instanceof MoveItemAction && lastAction.startPos == this.startPos) {
// action MoveItemAction, action
// action
this.prevItem = lastAction.prevItem
return history.pop() // pop action
} else {
// ,
this.prevItem = history.state.items.get(this.itemId)
return history
}
}
next(state: State): State {
const dx = this.movingPos.x - this.startPos.x
const dy = this.movingPos.y - this.startPos.y
const moved = this.prevItem.move(dx, dy)
return state.setIn(['items', this.itemId], moved)
}
prev(state: State) {
// prevItem
return state.setIn(['items', this.itemId], this.prevItem)
}
getMessage() { /* ... */ }
}
위의 코드 에서 볼 수 있 듯 이 prepare 방법 은 action 자체 가 준비 되 는 것 외 에 역사 기록 도 준비 할 수 있 습 니 다.서로 다른 Action 유형 은 서로 다른 합병 규칙 이 있 고 모든 Action 에 합 리 적 인 prepare 함 수 를 실현 한 후에 재 작업 기능 을 취소 하 는 사용자 체험 을 크게 향상 시 킬 수 있 습 니 다.다른 주의해 야 할 점 들
리 셋 기능 을 취소 하 는 것 은 매우 가 변성 에 의존 하 는 것 입 니 다.하나의 Action 대상 이 Apphistory.list 를 넣 은 후에 인 용 된 대상 은 모두 가 변 적 이지 않 아야 합 니 다.action 에서 인용 한 대상 이 바 뀌 었 다 면 후속 취소 시 오류 가 발생 할 수 있 습 니 다.이 방안 에 서 는 작업 이 발생 했 을 때 필요 한 정 보 를 기록 하 는 데 편리 하도록 Action 대상 의 prepare 방법 에서 제자리 수정 작업 이 허용 되 지만 prepare 방법 은 action 이 역사 기록 에 들 어가 기 전에 한 번 만 호출 됩 니 다.action 이 기록 목록 에 들 어가 면 변 할 수 없습니다.
총결산
이상 은 실 용적 인 취소 기능 을 실현 하 는 모든 절차 이다.서로 다른 전단 프로젝트 는 서로 다른 수요 와 기술 방안 이 있 기 때문에 위의 코드 가 프로젝트 에서 한 줄 도 사용 하지 못 할 수도 있 습 니 다.그러나 재 작업 을 취소 하 는 사고방식 은 같 아야 한다.본 고가 너 에 게 약간의 깨 우 침 을 줄 수 있 기 를 바란다.
위 에서 말 한 것 은 임 뮤 타 블 리 제 이 스 를 기반 으로 재 작업 취소 기능 을 실현 하 는 인 스 턴 스 코드 입 니 다.여러분 에 게 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.소 편 은 제때에 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Immutable.js 기반 리 셋 취소 기능 의 인 스 턴 스 코드모든 Action 이 적용 되 기 전에 prepare 방법 이 호출 됩 니 다.prepare 방법 에서 역사 기록 을 수정 할 수 있 습 니 다.예 를 들 어 MoveItemAction 에 대해 저 희 는 이전 ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.