*리액트를 사용하고 있는 대부분의 사이트들은 16버전을 사용
16에서 17버전으로 업그레이드 시 크게 어려움이 없음
16버전까지는 이벤트가 document에서 위임이 됐지만, 17부터는 최상단 트리인 루트에서 이벤트 위임이 됨
단점
import React 구문을 없애므로 인해 번들링 크기를 줄이고, 컴포넌트 작성을 더욱 간결하게 해줌
리액트 18의 가장 큰 변경점은 동시성 지원, 다양한 훅 추가
useId
컴포넌트별로 유니크한 값을 생성하는 새로운 훅
=>하나의 컴포넌트가 여러 군데 재사용되거나, 서버 사이드 렌더링 환경에서 하이드레이션이 발생할 때 서버와 클라이언트에서 동일한 값을 가져야 될 때 등의 경우에 유니크한 값을 생성시켜줌
useTransition
UI 변경을 가로막지 않고 상태를 업데이트 할 수 있는 리액트 훅
=> 무거운 렌더링 작업을 뒤로 미룸으로써, 사용자 경험을 최적화하기 좋음
=> 로딩 화면 및 진행 중인 렌더링을 버리고 새로운 상태값으로 렌더링 하는 등의 동시성을 지원
useDeferredValue
리액트 컴포넌트 트리에서 리렌더링이 급하지 않은 부분을 지연할 수 있게 도와주는 훅
=> useTransition과 비슷한 역할을 하지만 useTransition은 state 값을 업데이트 하는 함수를 사용하고, useDeferredValue는 state값 자체를 사용
*리액트 18로 버전 업그레이드 시 반드시 index.ts(js)를 변경해야 함
createRoot
기존의 render 메서드를 리액트 18로 버전 업그레이드시 createRoot와 render를 함께 사용해야 함
hydrateRoot
서버 사이드 렌더링 애플리케이션에서 하이드레이션을 하기 위한 메서드
=> react-dom/server에 의해 생성된 HTML 컨텐츠를 가지고 있는 브라우저 DOM node 내부에 리액트 컴포넌트들을 보여질 수 있도록 함
자체적으로 서버 사이드 렌더링 구현시 수정 필요
리액트가 여러 상태 업데이트를 하나의 리렌더링으로 묶어 성능을 향상
import { Profiler, useCallback, useEffect, useState } from 'react';
function App() {
const sleep = (ms) => {
return new Promise((res) => setTimeout(res, ms));
};
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const callback = useCallback((id, ph, ac, ba, st, co) => {
console.group(ph);
console.table({ id, ph, co });
console.groupEnd();
}, []);
useEffect(() => {
console.log('rendered');
});
function handleClick() {
sleep(3000).then(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
});
}
return (
<div className="App">
<Profiler onRender={callback}>
<header className="App-header">
<button onClick={handleClick}>click</button>
<h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1>
</header>
</Profiler>
</div>
);
}
export default App;
react 17
react 18
=> 리액트 17에서도 비동기 이벤트에서는 자동 배치가 이뤄지지 않아 일관성이 없었지만 리액트 18 버전붙터는 루트 컴포넌트를 createRoot를 사용해서 만들면 모든 업데이트가 배치 작업으로 최적화할 수 있게 됐음
리액트 엄격 모드는 개발자 모드에서만 동작하고, 리액트 어플리케이션에서 발생할 수 있는 잠재적 버그를 찾는 데 도움이 되는 컴포넌트
// 문자열 ref
class UnsafeClass extends Component {
componentDidMount() {
console.log(this.refs.myInput)
}
render(){
return(
<div>
<input ref="myInput" type="text" />
</div>
)
}
}
// 콜백 ref
const callbackRef = useCallback((node) => {
// 후속 작업, node에는 div 엘리먼트의 값이 들어오게 된다.
},[])
<div ref={callbackRef} />
=> 함수형 프로그래밍의 원칙에 따라 모든 컴포넌트는 항상 순수하다고 가정하는데, 항상 순수한 결과물을 내는지 개발자에게 확인시켜 주기 위해 로그가 두 번 실행됨
향후 리액트에서 컴포넌트가 마운트가 해제된 상태에서도 컴포넌트 내부의 값을 유지할 수 있는 기능을 제공할 예정인데, 이를 위해 컴포넌트가 최초 마운트될 때 자동으로 모든 컴포넌트를 마운트 해제하고 두번째 마운트에서 이전 상태를 복원하게 된다. 이 기능은 오직 개발 모드에서만 적용됨
Suspense는 컴포넌트를 동적으로 가져올 수 있게 도와주는 기능인데, 첫 번째 인수는 fallback props로 지연시켜 불러온 컴포넌트를 불러오지 못했을 때 보여주는 fallback이다.
children으로는 React.lazy로 선언한 지연 컴포넌트를 받는다.
=> 지연 컴포넌트를 로딩하기 전에 fallback을 보여주고, 로딩 완료 시 fallback 대신 지연 컴포넌트를 보여주게 된다.
*기존 Actions
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
리액트 19에서는 이런 처리를 위한 훅이 제공됨
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = async () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
const [error, submitAction, isPending] = useActionState(async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// You can return any result of the action.
// Here, we return only the error.
return error;
}
// handle success
});
function ChangeName({currentName, onUpdateName}) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName);
onUpdateName(updatedName);
};
return (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
updateName 요청이 진행되는 동안 optimisticName을 렌더링하고, 업데이트 완료 혹은 오류 발생 시 리액트는 자동으로 currentName값으로 다시 전환한다.
// useEffect 사용
function Person() {
const [person, setPerson] = useState(null);
useEffect(() => {
fetchPerson().then((data) => setPerson(data));
}, []);
if (!person) return <h1>불러오는 중...</h1>;
return <h1>{person.name}</h1>;
}
// use 사용
function Person() {
const person = use(fetchPerson());
return <h1>{person.name}</h1>;
}
function App() {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Person />
</Suspense>
);
}
use는 데이터를 가져오는 동안 promise를 반환하는 함수에 사용될 수 있다.
Suspense를 사용해 가져오는 동안 UI를 표시해주고, promise가 resolve 될 때 데이터를 보여줄 수 있다.
// useContext 사용
const UserContext = createContext();
function User() {
const user = useContext(UserContext);
return <h1>안녕 {user.name}!</h1>;
}
function App() {
return (
<UserContext.Provider value={{ name: "Yongveloper" }}>
<User />
</UserContext.Provider>
);
}
// use 사용
const UserContext = createContext();
function User() {
const user = use(UserContext);
return <h1>안녕 {user.name}!</h1>;
}
function App() {
return (
<UserContext value={{ name: "Yongveloper" }}>
<User />
</UserContext>
);
}
useContext 대신 use로 사용할 수 있고 Context.Provider 대신 Context를 프로바이더로 렌더링할 수 있음