배치 작업 - 순수한 불변성이 가치가 없을 때
우리는 불변성이 부작용을 피하는 좋은 방법이라는 것을 보았습니다. 그렇다면 왜(그리고 언제) 누군가 그것을 사용하지 않을까요?
사용 사례 예
UI 애플리케이션에 다음이 포함되어 있다고 상상해 보십시오.
이메일 등록/해제 기능 이메일 문자열 배열을 받은 다음 그에 따라 세트를 업데이트합니다.
let emailSet = new Set([
'[email protected]',
'[email protected]',
'[email protected]',
//...
]);
const registerEmails = (list: string[]) => {
list.forEach(email => {
emailSet = new Set(emailSet).add(email)
})
}
const unregisterEmails = (list: string[]) => {
list.forEach(email => {
emailSet = new Set(emailSet).delete(email)
})
}
💡 컨셉에 대한 설명은 편하게 확인해주세요.
두 함수 모두 변경 없이 업데이트됩니다
emailSet
. 항상 Set with new Set()
생성자의 새 복사본을 만든 다음 최신 버전만 변경합니다. 이는 다음과 같은 몇 가지 과제를 제시합니다.제약 - 복제 비용이 많이 들 수 있음
세트를 복제할 때 각 항목이 새 세트로 복사되므로 복제에 소요된 총 시간은 세트 크기
O(size(Set))
에 비례합니다. 이것이 원래 세트의 부작용을 피하면서 가능한 한 복제를 피해야 하는 주된 이유입니다.문제 #1 - 변경되지 않은 세트 복제
다음과 같은 경우 불필요한 복제가 수행됩니다.
이것은 수정하기 쉽습니다. "선택적 복제"를 수행하도록 기능을 업데이트할 수 있습니다(실제 수정이 있는 경우에만 Set 변경).
const registerEmails = (list: string[]) => {
list.forEach(email => {
/* Check if email not registered before cloning */
if (!emailSet.has(email)) {
emailSet = new Set(emailSet).add(email)
}
})
}
const unregisterEmails = (list: string[]) => {
list.forEach(email => {
/* Check if email registered before cloning */
if (emailSet.has(email) {
emailSet = new Set(emailSet).delete(email)
}
})
}
💡 클라이언트 측 프레임워크(예: Angular, React 등)는 일반적으로
===
테스트에 의존하여 구성 요소 변경 사항을 감지합니다. 쓸모없는 복제를 강제하면 복제 프로세스와 프레임워크 내부 diff 검사 모두에서 시간이 낭비됩니다.문제 #2 - 돌연변이를 일괄 처리하지 않음
우리 코드는 특정 상황에서 여전히 성능이 좋지 않습니다. 등록/등록 해제할 10개의 이메일 목록을 수신하면 세트가 forEach
루프 내에서 10번 복제될 수 있습니다.
registerEmails([
'[email protected]', // New email, clone Set
'[email protected]', // New email, clone Set
'[email protected]', // New email, clone Set
//... (New email, clone Set x7)
])
Is it possible to keep the immutability benefits while also speeding up code execution? We still want a new Set, but this much cloning is not desired in this case.
배치
위의 문제에 대한 해결책을 일괄 처리라고 합니다. 일괄 처리 컨텍스트 외부에서 보면 모든 것이 변경 불가능해 보이지만(부작용 없음) 내부에서는 가능한 경우 변경 가능성을 사용합니다.
배처는 대상 개체(이 경우 Set)를 래핑하고 규칙을 따르는 변경을 위한 API를 제공합니다.
registerEmails([
'[email protected]', // New email, clone Set
'[email protected]', // New email, clone Set
'[email protected]', // New email, clone Set
//... (New email, clone Set x7)
])
Is it possible to keep the immutability benefits while also speeding up code execution? We still want a new Set, but this much cloning is not desired in this case.
위의 문제에 대한 해결책을 일괄 처리라고 합니다. 일괄 처리 컨텍스트 외부에서 보면 모든 것이 변경 불가능해 보이지만(부작용 없음) 내부에서는 가능한 경우 변경 가능성을 사용합니다.
배처는 대상 개체(이 경우 Set)를 래핑하고 규칙을 따르는 변경을 위한 API를 제공합니다.
절대적으로 필요할 때까지 복제 대상을 지연합니다(전화
willChange()
). currentValue
). registerEmails
함수의 배처를 예로 들어 보겠습니다.const registerEmails = (list: string[]) => {
/* Create the batcher context for emailSet */
let batcher = prepareBatcher(emailSet);
list.forEach(email => {
/* Use batcher currentValue property to refer to Set */
if (!batcher.currentValue.has(email)) {
/* Let batcher know a change is about to happen */
batcher.willChange();
/* We can mutate currentValue (Set) directly now */
batcher.currentValue.add(email)
/* Update our emailSet variable */
emailSet = batcher.currentValue;
}
})
}
구성 가능한 Batcher
이전 코드는 성능이 우수하지만 일괄 처리 아키텍처에 코드 재사용 가능성이 존재할 수 있습니다. 이를 구현하는 한 가지 방법은 다음과 같습니다.
이전 코드 스니펫을 더 재사용 가능한 함수로 리팩터링해 보겠습니다.
/* This can be reused for any Set */
const add = <T>(batcher: Batcher<Set<T>>, item: T) => {
if (!batcher.currentValue.has(item)) {
batcher.willChange();
batcher.currentValue.add(item);
}
return batcher;
}
/* This can be reused for any Set */
const remove = <T>(batcher: Batcher<Set<T>>, item: T) => {
if (batcher.currentValue.has(item)) {
batcher.willChange();
batcher.currentValue.delete(item);
}
return batcher;
}
이제 함수를 프로젝트로 가져올 수 있습니다.
const registerEmails = (batcher: Batcher<Set<string>>, list: string[]) => {
list.forEach(email => {
add(batcher, email);
});
return batcher;
}
const unregisterEmails = (batcher: Batcher<Set<string>>, list: string[]) => {
list.forEach(email => {
remove(batcher, email);
});
return batcher;
}
/* Call registerEmails */
let batcher = prepareBatcher(emailSet);
registerEmails(batcher, [...]);
emailSet = batcher.currentValue;
더 높은 수준의 절차를 계속 만들 수 있습니다.
const complexOperation = (batcher: Batcher<Set<string>>) => {
/* Apply operations */
registerEmails(batcher, [...]);
unregisterEmails(batcher, [...]);
unregisterEmails(batcher, [...]);
registerEmails(batcher, [...]);
return batcher;
}
let batcher = prepareBatcher(emailSet);
/* Call the function */
complexOperation(batcher);
/* Update variable */
emailSet = batcher.currentValue;
length(array)
내부의 각 등록/등록 취소 호출에 대해 size(Set)
개의 복제본(complexOperation
개의 항목 복사본 포함)이 있었을 수 있습니다. prepareBatcher(emailSet)
를 호출하여 함수에 제공하기만 하면 됩니다. 변경 사항이 없는 경우 참조 동등성은 여전히 개체를 나타냅니다.
개념의 증거
저는 최근에 Batcher 아키텍처에 대한 개념 증명을 제시했습니다. 아래 CodeSandbox 예제에서 console.log
s를 확인할 수 있습니다.
소스 코드는 다음에서 찾을 수 있습니다.
스택코메이트
/
데이터 구조
Typescript 프로젝트를 위한 데이터 구조.
지금은 add
, remove
및 filter
방법을 사용할 수 있습니다. 새로운 작업이 곧 제공될 예정입니다.
Reference
이 문제에 관하여(배치 작업 - 순수한 불변성이 가치가 없을 때), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/rafaelcalpena/batching-operations-when-pure-immutability-is-not-worth-it-22o6
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(배치 작업 - 순수한 불변성이 가치가 없을 때), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rafaelcalpena/batching-operations-when-pure-immutability-is-not-worth-it-22o6텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)