theme | highlighter | favicon | title | info | colorSchema | fonts | ||
---|---|---|---|---|---|---|---|---|
geist |
shiki |
/images/favicon.ico |
νλ‘ νΈμλμμ ν¨μνμ μΆκ΅¬νλ©΄ μλλ κ±ΈκΉ? |
ν¨μν νλ‘κ·Έλλ°μ λν μ΄λ‘ κ³Ό νλ‘ νΈμλ κ°λ°μ νλ©΄μ ν¨μν νλ‘κ·Έλλ°μ μ μ©ν μ¬λ‘λ₯Ό μκ°ν©λλ€. |
light |
|
κΉλ―Όμ, μ΄μ°½ν¬
κΉλ―Όμ
λ°λΈμμ€ν°μ¦
Software Engineer (Frontend Engineer)
JavaScript, TypeScript, ReactJS, GatsbyJS,
GraphQL, Functional Programming, β¦
- alstn2468
- minsu._.0102
- minsu-kim-developer
- https://alstn2468.github.io
μ΄μ°½ν¬
ε λ°λΈμμ€ν°μ¦
Software Engineer (Frontend/Backend Engineer)
JavaScript, TypeScript, Golang, Python, β¦
- blurfx
- blureffect
- blurfx
- https://xo.dev
λ°μ΄ν°λ₯Ό μνμ κ³μ° ν¨μ(μμ ν¨μ)λ‘ μ²λ¦¬νκ³
μ¬μ΄λ μ΄ννΈλ₯Ό λ©λ¦¬νλ νλ‘κ·Έλλ° ν¨λ¬λ€μ
νλ‘ νΈμλ κ°λ°μ νλ©΄μ μ¬μ΄λ μ΄ννΈλ₯Ό λ©λ¦¬ν μ μλμ?
DOM μ‘°μ, λ°μ΄ν° ν¨μΉ λͺ¨λ μ¬μ΄λ μ΄ννΈλ₯Ό λ°μμν¬ κ² κ°μλ°μ?
μ¬λ¬ λ°©λ²μ ν΅ν΄ λΆμ ν¨μμ μμ ν¨μλ₯Ό ν¨κ» κ΄λ¦¬νκ³
μ΄λ¬ν ν¨μλ€μ μ‘°ν©ν΄ νλ‘κ·Έλ¨μ λ§λλ ν¨λ¬λ€μ
νλμ νΌμ 8κ°μ μ λ ₯μ΄ μ‘΄μ¬
κ°κ° λ€λ₯Έ κ²μ¦ λ‘μ§μ΄ μ‘΄μ¬
곡ν΅μΌλ‘ μ¬μ©ν μ μλ κ²μ¦ κ³Όμ μ‘΄μ¬
- 1. Haskell, PureScript, Scala κΈ°λ°μ μΈκΈ°μλ νμ μΆμνλ₯Ό μ 곡
- 2. ꡬνλ νμ ν΄λμ€λ μμ νκ² μ‘°ν©λ μ μλλ‘ μΆμ λμνκ³Ό λ²μ£Όλ‘ μ κΈ°λ°
- 3. Typescriptμμ μ§μνμ§ μλ HKT(Higher Kinded Types) ꡬν
- 4. io-ts κ°μ΄ μ¬μ©ν μ μλ λ§μ λΌμ΄λΈλ¬λ¦¬ λν μ‘΄μ¬ (fp-ts/ecosystem)
type None = { _tag: 'None' };
type Some<A> = { _tag: 'Some', value: A };
type Option<A> = None | Some<A>;
Option<A>λ μ νμ μΈ κ° Aλ₯Ό μν 컨ν μ΄λ μ λλ€.
A νμ
μ κ°μ΄ μ‘΄μ¬νλ€λ©΄ Option<A>λ Some<A> μΈμ€ν΄μ€μ
λλ€.
κ°μ΄ μ‘΄μ¬νμ§ μλλ€λ©΄ Option<A>λ None μΈμ€ν΄μ€μ λλ€.
Option<A>λ μ€ν¨ν μ μλ κ³μ°μ ν¨κ³Όλ₯Ό λνλ λλ€.
type None = { _tag: 'None' };
type Some<A> = { _tag: 'Some', value: A };
type Option<A> = None | Some<A>;
import { Option, some, none } from 'fp-ts/lib/Option';
function findIndex<A>(
as: Array<A>,
predicate: (a: A) => boolean
): Option<number> {
const index = as.findIndex(predicate);
return index === -1 ? none : some(index);
}
const arr = [1, 2, 3];
findIndex(arr, (n) => n === 1); // { _tag: 'Some', value: 0 }
findIndex(arr, (n) => n === 4); // { _tag: 'None' }
type None = { _tag: 'None' };
type Some<A> = { _tag: 'Some', value: A };
type Option<A> = None | Some<A>;
import { fromNullable } from 'fp-ts/lib/Option';
fromNullable(undefined); // { _tag: 'None' }
fromNullable(null); // { _tag: 'None' }
fromNullable(0); // { _tag: 'Some', value: 0 }
type None = { _tag: 'None' };
type Some<A> = { _tag: 'Some', value: A };
type Option<A> = None | Some<A>;
import { fromPredicate } from 'fp-ts/lib/Option';
const isNumber = <T>(a: T) => !isNaN(Number(a));
const getOptionNumber = fromPredicate(isNumber);
getOptionNumber('a') // { _tag: 'None' }
getOptionNumber('10'); // { _tag: 'Some', value: '10' }
getOptionNumber(1); // { _tag: 'Some', value: 1 }
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
Either<E,A>λ λ κ°μ νμ μ€ νλμ κ°μ ννν©λλ€. (λΆλ¦¬ ν©μ§ν©, Disjoint Union)
Eitherμ μΈμ€ν΄μ€λ Left λλ Right μΈμ€ν΄μ€ μ λλ€.
Eitherλ κ²°μΈ‘κ°μ μ²λ¦¬νκΈ° μν΄ Option λμ μ μ¬μ©ν μ μμ΅λλ€.
Optionμ Noneμ μ 보λ₯Ό ν¬ν¨ν μ μλ Leftλ‘ λ체 λ©λλ€.
μΌλ°μ μΌλ‘ Leftλ μ€ν¨λ₯Ό νννκ³ Rightλ μ±κ³΅μ ννν©λλ€.
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
import { Either, tryCatch } from 'fp-ts/lib/Either';
function parse(s: string): Either<Error, unknown> {
return tryCatch(
() => JSON.parse(s),
(reason) => new Error(String(reason)),
);
}
const success = '{"a": 1, "b": 2}';
const fail = '{"a": 1, "b"}';
parse(success); // { _tag: 'Right', right: { a: 1, b: 2 } }
parse(fail); // { _tag: 'Left', left: 'Error: SyntaxError: Unexpected token...' }
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
import { fromNullable } from 'fp-ts/lib/Either';
const getEitherString = fromNullable('defaultValue');
getEitherString(null); // { _tag: 'Left', left: 'defaultValue' }
getEitherString(undefined); // { _tag: 'Left', left: 'defaultValue' }
getEitherString('value'); // { _tag: 'Right', right: 'value' }
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
import { fromPredicate } from 'fp-ts/lib/Either';
const isEmptyString = (s: string) => s === '';
const getEitherString = fromPredicate(
(s: string) => !isEmptyString(s),
() => 'defaultValue',
);
getEitherString(''); // { _tag: 'Left', left: 'defaultValue' }
getEitherString('abc'); // { _tag: 'Right', right: 'abc' }
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
type Task<A> = { (): Promise<A> };
type TaskEither<E, A> = Task<Either<E, A>>;
Task<A>λ A νμ μ κ°μ λ°ννλ λΉλκΈ° κ³μ°μ ννν©λλ€.
Task<A>λ μ λ μ€ν¨νμ§ μλ λΉλκΈ° κ³μ°μ μ¬μ©λ©λλ€.
μ€ν¨ν μ μλ λΉλκΈ° κ³μ°μ TaskEither<E,A>λ₯Ό μ¬μ©ν μ μμ΅λλ€.
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
type Task<A> = { (): Promise<A> };
type TaskEither<E, A> = Task<Either<E, A>>;
import { Task } from 'fp-ts/lib/Task';
const read: Task<string> = () => {
return new Promise<string>((resolve) => {
const rl = createInterface({
input: process.input,
output: process.stdout,
});
rl.question('Input: ', (answer) => {
rl.close();
console.log(answer);
resolve(answer);
});
});
}
read();
type Left<E> = { _tag: 'Left', left: E };
type Right<A> = { _tag: 'Right', right: A };
type Either<E, A> = Left<E> | Right<A>;
type Task<A> = { (): Promise<A> };
type TaskEither<E, A> = Task<Either<E, A>>;
import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither';
function taskEitherTest(isResolve: boolean): TaskEither<string, string> {
return tryCatch(
() => isResolve
? Promise.resolve('resolved')
: Promise.reject('rejected'),
() => 'fall back string',
);
}
async function run() {
const resolve = taskEitherTest(true);
const reject = taskEitherTest(false);
console.log(await resolve());
console.log(await reject());
}
run();
flowchart LR
1 -- add1 --> 2 -- add2 --> 4 -- add3 --> 7
pipeλ₯Ό μ¬μ©νμ§ μκ³ ν¨μλ₯Ό ν©μ±νλ κ²½μ°
const add = (a: number) => (b: number) => a + b;
const add1 = add(1);
const add2 = add(2);
const add3 = add3(3);
add3(add2(add1(1))); // 7
flowchart LR
1 -- add1 --> 2 -- add2 --> 4 -- add3 --> 7
pipeλ₯Ό μ¬μ©νμ§ μμμ λ ν©μ±λλ ν¨μμ μκ° μ μ λ§μμ§λ€λ©΄ μ΄λ»κ² λ κΉμ??
flowchart LR
1 -- add1 --> 2 -- add2 --> 4 -- add3 --> 7
pipeλ₯Ό μ¬μ©ν΄ ν¨μλ₯Ό ν©μ±νλ κ²½μ°
import { pipe } from 'fp-ts/lib/function';
const add = (a: number) => (b: number) => a + b;
const add1 = add(1);
const add2 = add(2);
const add3 = add3(3);
pipe(1, add1, add2, add3);
pipe(1, add1, add2, add3, add3, add3, add3, add3, add3);
declare const optionMap: <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>;
declare const taskMap: <A, B>(f: (a: A) => B) => (fa: Task<A>) => Task<B>;
declare const eitherMap: <A, B>(f: (a: A) => B) => <E>(fa: Either<E, A>) => Either<E, B>;
declare const taskEitherMap: <A, B>(f: (a: A) => B) => <E>(fa: TaskEither<E, A>) => TaskEither<E, B>;
mapν¨μλ μ¬μ ν¨μλΌκ³ νλ©° A νμ μ κ°μ B νμ μ κ°μΌλ‘ λ°κΏ λ μ¬μ©ν μ μμ΅λλ€.
mapν¨μλ 곡ν΅μ μΌλ‘ (f: (a: A) => B) μκ·Έλμ²λ₯Ό κ°λ ν¨μλ₯Ό μ λ¬λ°μ΅λλ€.
import { fromNullable, map } from 'fp-ts/lib/Option';
pipe(
'something value', // string
fromNullable, // Option<string>
map((value) => value.length), // Option<number>
map((value) => value + 1), // Option<number>
map((value) => value.toString()), // Option<string>
);
declare const optionMap: <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>;
declare const taskMap: <A, B>(f: (a: A) => B) => (fa: Task<A>) => Task<B>;
declare const eitherMap: <A, B>(f: (a: A) => B) => <E>(fa: Either<E, A>) => Either<E, B>;
declare const taskEitherMap: <A, B>(f: (a: A) => B) => <E>(fa: TaskEither<E, A>) => TaskEither<E, B>;
mapν¨μλ μ¬μ ν¨μλΌκ³ νλ©° A νμ μ κ°μ B νμ μ κ°μΌλ‘ λ°κΏ λ μ¬μ©ν μ μμ΅λλ€.
mapν¨μλ 곡ν΅μ μΌλ‘ (f: (a: A) => B) μκ·Έλμ²λ₯Ό κ°λ ν¨μλ₯Ό μ λ¬λ°μ΅λλ€.
import { fromPredicate, map } from 'fp-ts/lib/Option';
pipe(
1, // number
fromPredicate((value) => value < 0), // Option<number>
map((value) => value * value), // Option<number>
map((value) => [value]), // Option<Array<number>>
);
const eiterhChain = <E, A, B>(f: (a: A) => Either<E, B>) => (
ma: Either<E, A>,
): Either<E, B> => (isLeft(ma) ? ma : f(ma.right));
chainν¨μλ λ€μ κ³μ°μ ν μ§λ§μ§ κ²°μ νκΈ° μν΄ μ¬μ©λλ©°
μ κ³μ°μ λ°ν κ°μ μ΄μ©ν΄ μμλλ‘ κ³μ°μ μ§νν©λλ€.
import { Either, chain, left, right } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
const multiplyByTen = <T>(value: T): Either<string, number> =>
typeof value === 'number' ? right(value * 10) : left('Not a number');
const increment = (value: number): Either<string, number> => right(value + 1);
const func = <T>(value: T) => pipe(
value,
multiplyByTen,
chain(increment),
);
func('Hello World!');
const eiterhChain = <E, A, B>(f: (a: A) => Either<E, B>) => (
ma: Either<E, A>,
): Either<E, B> => (isLeft(ma) ? ma : f(ma.right));
chainν¨μλ λ€μ κ³μ°μ ν μ§λ§μ§ κ²°μ νκΈ° μν΄ μ¬μ©λλ©°
μ κ³μ°μ λ°ν κ°μ μ΄μ©ν΄ μμλλ‘ κ³μ°μ μ§νν©λλ€.
import { Either, chain, left, right } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
const multiplyByTen = <T>(value: T): Either<string, number> =>
typeof value === 'number' ? right(value * 10) : left('Not a number');
const increment = (value: number): Either<string, number> => right(value + 1);
const func = <T>(value: T) => pipe(
value,
multiplyByTen,
chain(increment),
);
func(10);
declare const optionMatch: <A, B>(onNone: () => B, onSome: (a: A) => B) =>
(ma: Option<A>) => B;
declare const eitherMatch: <E, A, B>(onNone: (e: E) => B, onSome: (a: A) => B) =>
(ma: Either<E, A>) => B;
matchμ fold ν¨μλ λμΌν κΈ°λ₯μ μννλ©° κ°λ₯ν μΌμ΄μ€μ λ°λΌ μ€νν ν¨μλ₯Ό λ°μ μ€νν©λλ€.
import { fromPredicate, match } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/function';
pipe(
2,
fromPredicate((value) => value !== 0),
match(() => 0, (value) => 10 / value),
);
declare const optionMatch: <A, B>(onNone: () => B, onSome: (a: A) => B) =>
(ma: Option<A>) => B;
declare const eitherMatch: <E, A, B>(onNone: (e: E) => B, onSome: (a: A) => B) =>
(ma: Either<E, A>) => B;
matchμ fold ν¨μλ λμΌν κΈ°λ₯μ μννλ©° κ°λ₯ν μΌμ΄μ€μ λ°λΌ μ€νν ν¨μλ₯Ό λ°μ μ€νν©λλ€.
import { fromPredicate, match } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/function';
pipe(
0,
fromPredicate((value) => value !== 0),
match(() => 0, (value) => 10 / value),
);
νΌμ 8κ°μ μ λ ₯μ΄ μ‘΄μ¬νλλ°, μ΄κ²μ μ΄λ»κ² μ°μνκ² μ²λ¦¬ν μ μμκΉμ?
- μ¬μ©μμκ² κ° μ λ ₯λ°κΈ°
- μ λ ₯λ κ° κ²μ¦νκΈ°
- κ²μ¦μ μ€ν¨νλ€λ©΄ μ€λ₯ λ©μμ§ λ³΄μ¬μ£ΌκΈ°
const [mobileNumber, setMobileNumber] = React.useState<string>('');
const [mobileNumberError, setMobileNumberError] = React.useState<string>('');
// μ¬λ°λ₯Έ νμμ ν΄λν° λ²νΈμΈμ§ κ²μ¦νλ ν¨μ
const validateMobileNumber = (value: string): boolean => {
if (value == '') {
setMobileNumberError('ν΄λν° λ²νΈλ₯Ό μ
λ ₯ν΄μ£ΌμΈμ.');
return false;
}
if (!mobileNumberRegex.test(value)
|| !value.startswith('01')
|| value.length < 10
|| value.length > 11
) {
setMobileNumberError('ν΄λν° λ²νΈκ° μ¬λ°λ₯΄μ§ μμ΅λλ€.');
return false;
}
return true;
}
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => {
const { value } = e.target;
validateMobileNumber(value);
setMobileNumber(value);
}
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validations = [validateMobileNumber(mobileNumber), ...];
if (validations.some((valid) => !valid)) {
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberError}</span>
...
)
κ²μ¦ κ°λ₯ν μ λ ₯ νλ νλλ₯Ό ꡬννκΈ° μν΄ μ°λ¦¬λ μλμ κ²λ€μ΄ νμν©λλ€.
- 2κ°μ React.useState (μ λ ₯ κ°, μ€λ₯ λ©μμ§)
- 1κ°μ νλ κ²μ¦ ν¨μ
- κ²μ¦ ν¨μλ₯Ό νΈμΆνκ³ μ λ ₯ κ° μνλ₯Ό κ΄λ¦¬νλ 1κ°μ μ΄λ²€νΈ νΈλ€λ¬ μ½λ°± ν¨μ
const [mobileNumber, setMobileNumber] = React.useState<string>('');
const [mobileNumberError, setMobileNumberError] = React.useState<string>('');
const [name, setName] = React.useState<string>('');
const [nameError, setNameError] = React.useState<string>('');
const [email, setEmail] = React.useState<string>('');
const [emailError, setEmailError] = React.useState<string>('');
const validateMobileNumber = (value: string): boolean => {
/* ... */
};
const validateName = (value: string): boolean => {
/* ... */
};
const validateEmail = (value: string): boolean => {
/* ... */
};
const handleMobileNumberChange = (e) => {
/* ... */
};
const handleNameChange = (e) => {
/* ... */
};
const handleEmailChange = (e) => {
/* ... */
};
π€
- μ λ ₯ κ°κ³Ό μ€λ₯ λ©μμ§λ₯Ό κ΄λ¦¬νλ λ κ°μ μνκ° μμ΅λλ€.
- μ¬μ©μκ° κ°μ μ λ ₯νλ©΄ μνλ₯Ό κ°±μ ν©λλ€.
- κ²μ¦ ν¨μλ₯Ό ν΅ν΄ μ¬μ©μκ° μ λ ₯ν κ°μ΄ μ¬λ°λ₯Έμ§ κ²μ¦ν©λλ€.
- κ²μ¦μ μ€λ₯κ° μλ€λ©΄ μ€λ₯ λ©μμ§ μνλ₯Ό κ°±μ ν©λλ€.
- μ λ ₯ κ°κ³Ό μ€λ₯ λ©μμ§λ₯Ό κ΄λ¦¬νλ λ κ°μ μνκ° μμ΅λλ€.
- μ¬μ©μκ° κ°μ μ λ ₯νλ©΄ μνλ₯Ό κ°±μ ν©λλ€.
- κ²μ¦ ν¨μλ₯Ό ν΅ν΄ μ¬μ©μκ° μ λ ₯ν κ°μ΄ μ¬λ°λ₯Έμ§ κ²μ¦ν©λλ€.
- κ²μ¦μ μ€λ₯κ° μλ€λ©΄ μ€λ₯ λ©μμ§ μνλ₯Ό κ°±μ ν©λλ€.
- 곡ν΅μ μΌλ‘ μ¬μ©ν μ μλ κ²μ¦ ν¨μ λ§λ€κΈ°
- κ²μ¦ κ·μΉ μ μνκΈ°
- μ λ ₯ νλ κ²μ¦κΈ° λ§λ€κΈ°
- 컀μ€ν ν λ§λ€κΈ°
import { fromPredicate } from 'fp-ts/Either';
import { pipe, type Predicate } from 'fp-ts/function';
import { every, map } from 'fp-ts/Array';
const validate = <T>(validators: Array<Predicate<T>>, errorMessage: string) => (value: T) => pipe(
value,
fromPredicate(
(val) => pipe(
validators,
map(fn => fn(val)),
every(Boolean),
),
() => errorMessage,
),
);
const my_validator = validate(myMobileNumberRules, 'μλͺ»λ μ νλ²νΈ νμμ
λλ€.');
my_validator('01012345678'); // right('01012345678')
my_validator('01aabb'); // left('μλͺ»λ μ νλ²νΈ νμμ
λλ€.')
const startsWith = (search: string): Predicate<string> => (text: string) => text.startsWith(search);
const minLength = (limit: number): Predicate<string> => (text: string) => text.length >= limit;
const maxLength = (limit: number): Predicate<string> => (text: string) => text.length <= limit;
const testPhoneNumberPattern = (text: string) => !/[^0-9]/gi.test(text);
const myMobileNumer = '010123456';
testPhoneNumberPattern(myMobileNumer); // true
startsWith('01')(myMobileNumer); // true
maxLength(11)(myMobileNumer); // true
minLength(10)(myMobileNumer); // false
import { chain } from 'fp-ts/Either';
import { pipe } from 'fp-ts/function';
export const validatePhoneNumber = (phoneNumber: string): Either<string, string> =>
pipe(
phoneNumber,
validate([minLength(1)], 'νμνλͺ©μ
λλ€.'), // μ무κ²λ μ
λ ₯λμ§ μμλμ§ κ²μ¬ν©λλ€.
chain(
validate(
[
testPhoneNumberPattern, // μ«μ μΈμ λ€λ₯Έ λ¬Έμκ° μλμ§ νμΈν©λλ€.
startsWith('01'), // ν΄λν° λ²νΈλ 01λ‘ μμν΄μΌν©λλ€.
minLength(10), // ν΄λν° λ²νΈμ κΈΈμ΄λ μ΅μ 10μμ¬μΌν©λλ€.
maxLength(11), // ν΄λν° λ²νΈμ κΈΈμ΄λ μ΅λ 11μμ¬μΌν©λλ€.
],
'μ¬λ°λ₯΄μ§ μμ λ²νΈνμμ
λλ€.'
)
)
);
validatePhoneNumber(''); // left('νμνλͺ©μ
λλ€.');
validatePhoneNumber('012323abc'); // left('μ¬λ°λ₯΄μ§ μμ λ²νΈνμμ
λλ€.');
validatePhoneNumber('01012345678'); // right('01012345678');
import * as Either from 'fp-ts/Either';
import * as string from 'fp-ts/string';
import { identity, pipe } from 'fp-ts/function';
type StateValidator = {
validate: () => boolean,
error: string,
};
const useStateWithValidator = <T>(initialState: T, validator: (v: T) => Either<string, T>):
[T, (v: T, t?: boolean) => void, StateValidator] => {
const [value, setValue] = useState<T>(initialState);
const [error, setError] = useState('');
const changeError = (e: string) => {
setError(e);
return e;
};
const changeValue = (v: T) => {
pipe(
validator(v),
Either.match(
identity,
() => pipe(
v,
setValue,
() => string.empty,
),
),
changeError,
);
};
const stateValidator: StateValidator = {
validate(): boolean {
return pipe(
validator(value),
Either.match(identity, () => string.empty),
changeError,
string.isEmpty,
);
},
get error(): string {
return error;
},
};
return [value, changeValue, stateValidator];
};
const [mobileNumber, setMobileNumber] = React.useState<string>('');
const [mobileNumberError, setMobileNumberError] = React.useState<string>('');
// μ¬λ°λ₯Έ νμμ ν΄λν° λ²νΈμΈμ§ κ²μ¦νλ ν¨μ
const validateMobileNumber = (value: string): boolean => {
if (value == '') {
setMobileNumberError('ν΄λν° λ²νΈλ₯Ό μ
λ ₯ν΄μ£ΌμΈμ.');
return false;
}
if (!mobileNumberRegex.test(value)
|| !value.startswith('01')
|| value.length < 10
|| value.length > 11
) {
setMobileNumberError('ν΄λν° λ²νΈκ° μ¬λ°λ₯΄μ§ μμ΅λλ€.');
return false;
}
return true;
}
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => {
const { value } = e.target;
validateMobileNumber(value);
setMobileNumber(value);
}
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validations = [validateMobileNumber(mobileNumber), ...];
if (validations.some((valid) => !valid)) {
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberError}</span>
...
)
const [mobileNumber, setMobileNumber, mobileNumberValidator] = useStateWithValidator<string>('', validatePhoneNumber);
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => {
const { value } = e.target;
validateMobileNumber(value);
setMobileNumber(value);
}
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validations = [validateMobileNumber(mobileNumber), ...];
if (validations.some((valid) => !valid)) {
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberError}</span>
...
)
const [mobileNumber, setMobileNumber, mobileNumberValidator] = useStateWithValidator<string>('', validatePhoneNumber);
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => setPhoneNumber(e.target.value);
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validations = [validateMobileNumber(mobileNumber), ...];
if (validations.some((valid) => !valid)) {
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberError}</span>
...
)
const [mobileNumber, setMobileNumber, mobileNumberValidator] = useStateWithValidator<string>('', validatePhoneNumber);
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => setPhoneNumber(e.target.value);
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validators = [mobileNumberValidator, ...];
const isInvalid = validators
.map((validator) => validator.validate())
.some((valid) => !valid);
if (isInvalid) {
// Do something when input is invalid
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberError}</span>
...
)
const [mobileNumber, setMobileNumber, mobileNumberValidator] = useStateWithValidator<string>('', validatePhoneNumber);
// ν΄λν° λ²νΈ inputμ onChange μ΄λ²€νΈ νΈλ€λ¬
const handleMobileNumberChange = (e) => setPhoneNumber(e.target.value);
// form onsubmit μ΄λ²€νΈ νΈλ€λ¬
const onSubmit = () => {
const validators = [phoneNumberValidator, ...];
const isInvalid = validators
.map((validator) => validator.validate())
.some((valid) => !valid);
if (isInvalid) {
// Do something when input is invalid
return;
}
// Submit Form ...
}
return (
...
<input onChange={handleMobileNumberChange} value={mobileNumber} />
<span className="error">{mobileNumberValidator.error}</span>
...
)
- ν¨μν νλ‘κ·Έλλ°μ΄ λμΆ© 무μμΈμ§
- fp-tsμ μ΄λ€ νμ κ³Ό μ νΈλ¦¬ν° ν¨μκ° μ‘΄μ¬νκ³ μ΄λ»κ² μ°λμ§
- κ° κ²μ¦μ νμν λ‘μ§λ€μ fp-tsλ₯Ό μ¬μ©ν΄ ν¨μνμΌλ‘ μΆμννλ λ°©λ²
- κ³΅ν΅ λΆλΆ λ¬Έμ λ₯Ό ν΄κ²°νλ ν¨μμ 컀μ€ν ν μ λ§λ€μ΄ μ¬μ©νλ μ μ₯μμμ ꡬνμ λ¨μν νλ λ°©λ²
- λμμ νμ (ADT)λ₯Ό μ¬μ©νμ¬ λ¬Έμ λ₯Ό λ μ μ μνκ³ ν΄κ²°ν μ μμμ΅λλ€.
- μ½λλ₯Ό μμ±νλ©° ν¨μν μ¬κ³ λ₯Ό ν μ μμμ΅λλ€.
- κ³Όμ° μ΄ ν¨μμμ μ΄ μΌμ νλ κ²μ΄ μ³μκΉ?
- μ¬μ΄λ μ΄ννΈ μ΅μν λ° μ μ νκ² μ¬μ©
- νμμ μλ‘μ΄ κΈ°μ μ΄λ κ°λ μ λμ ν λ μ΄λ»κ² ν΄μΌν μ§ μκ² λμμ΅λλ€.
::right::
- fp-tsλ₯Ό μ¨λ³΄μ§ μμκ±°λ νΉμ ν¨μν νλ‘κ·Έλλ° κ²½νμ΄ μλ€λ©΄, μ½λ μμ±μ μ΄λ €μμ κ²ͺμ μ μμ΅λλ€.
- ν¨μν νλ‘κ·Έλλ°μ μ μ©νμ§ μκ³ κ·Έλ₯ μμ±νλ μ½λκ°λ κ°λ μ±μ΄ μ’κ³ μ§§μ κ²½μ°λ μμμ΅λλ€.
- μ) Array λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ μ»΄ν¬λνΈλ‘ 맀ννλ μ½λ
- μ΅μ μλ£κ° λ§μ§ μμ΅λλ€.
- fp-ts λ©μΈν μ΄λκ° μμ±ν κΈμ΄ μμ§λ§, μ€λλμ΄ outdated λ λ΄μ©μ΄ λ§μμ΅λλ€.
- fp-tsμ 곡μ λ¬Έμκ° μΉμ ν νΈμ μλλλ€.