새 페이지 작업을 할 때 페이지 자체에 구성요소 작성을 시작하지 마세요. 각각이 충분히 독립적이고 전역 상태의 변경으로 인해 다시 렌더링이 발생하지 않도록 구성 요소 단위를 개별화하기 시작합니다. 예를 들어, 이 페이지를 만드는 작업이 주어졌다고 가정해 보세요.
제가 코딩을 배울 때 그랬듯이 이러한 모든 구성 요소를 단일 파일에 작성하고 싶은 유혹을 느낄 수도 있습니다.
// WRONG export const LoginPage = () => { const [userId, setUserId] = useState('') const [password, setPassword] = useState('') return ( <div> <div className="d-flex flex-row"> <div id="marketing-container"> <svg {...fbLogo} /> <h2>Connect with friends and the world around you on Facebook</h2> </div> <div id="login-form"> <div> <input id="user-id" value={userId} onChange={(e) => setUserId(e.target.value)} /> <input id="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button>Log in</button> <a href="/forgot-password">Forgot password</a> <br /> <button>Create new account</button> </div> <div> <strong>Create a page</strong> <span>for a celebrity, brand or business.</span> </div> </div> </div> </div> ) } export default LoginPage
그러나 현실적으로 이러한 요소 중 상당수는 향후 앱 전체에서 재사용될 수 있으며, 이는 다시 작성하거나 복사/붙여넣기해야 함을 의미합니다. 잘 정의된 디자인으로 작업할 때 요소는 레고처럼 사용될 가능성이 높으며, 로그인 페이지에 표시되는 로고는 대시보드 화면의 로고와 동일하거나 크기만 다를 수 있습니다. 사용자 ID 입력과 동일하며 디자인 측면에서는 사용자 편집 페이지의 입력과 동일할 것입니다.
다음 요점으로 넘어가면, 구성요소는 비즈니스 로직에서 표현 로직으로 분리되어야 합니다. 이것이 의미하는 바는 상태와 통신하는 부분이 자체 구성 요소여야 하며 이 구성 요소는 프레젠테이션 소품을 프레젠테이션 구성 요소에 전달한다는 것입니다.
// Presentational component const UserIdInputComponent = ({ value, onChange }) => <input value={value} onChange={onChange} /> // Logic component export const UserIdInput = () => { const [value, setValue] = useState('') const handleChange = (e) => setValue(e.target.value) return <UserIdInputComponent value={value} onChange={handleChange} /> }
이를 통해 상태 관리에서 분리된 프레젠테이션 구성 요소만 내보내 스토리북과 같은 도구가 제대로 작동할 수 있습니다. API를 호출하고 전역 상태를 변경하는 스토리북에 로직이 많은 구성 요소를 통합하는 것은 성가신 일이 될 수 있습니다. 이 접근 방식을 사용하면 다양한 소품에 따라 구성 요소가 시각적으로 어떻게 변경되는지 확인할 수 있습니다.
메인페이지로 돌아갑니다. 아마 내가 어디로 가는지 알 수 있을 것입니다. 같은 페이지에 모든 것을 쓰는 대신. 이 구성 요소를 재사용할 수 있는 방법, 상태에서 분리할 수 있는 방법, 이 구성 요소와 관련된 소품이 변경되지 않는 한 다시 렌더링되지 않도록 격리할 수 있는 방법을 생각해 보세요.
export const LoginPage = () => ( <div> <div className="d-flex flex-row"> <FbMarketing /> <LoginForm /> </div> </div> ) export default LoginPage
가장 좋은 시나리오는 코딩을 시작하는 방법입니다. 모든 것이 예상대로 작동하는지 확인한 후에 다시 돌아와서 리팩토링하는 것이 더 번거로울 것입니다. 화면에서 빨리 뭔가를 보고 싶은 마음이 들어서 불안함을 달래고 처음부터 제대로 된 구조를 구축해 나가고 싶습니다.
const FbLogo = () => ( <svg {...fbLogoAttributes} /> ) const FbSlogan = () => ( <h2>Connect with friends and the world around you on Facebook.</h2> ) const FbMarketing = () => ( <> <FbLogo /> <FbSlogan /> </> )
현재 모든 프레젠테이션은 다음과 같습니다. 이는 FbLogoSmall, FbLogoMedium 등으로 더욱 개별화될 수 있습니다.
이제 몇 가지 논리가 포함된 부분인 로그인 양식입니다. 로그인인지, 로그인인지, 로그인인지는 잘 모르겠지만 페이스북의 '로그인'이라는 용어를 사용하겠습니다.
각 구성 요소는 재사용이 가능하고 분리 및 격리되어야 한다는 점을 다시 한 번 상기시켜 주세요.
재사용 가능:
먼저 UserIdInput을 재사용 가능하게 만든 다음 이 접근 방식을 다른 비밀번호 입력에 복사합니다. 프로덕션 수준 입력에는 테스트 ID, 소품에 따라 변경되는 클래스, 아리아 속성, 자동 초점 등과 같은 다른 속성이 포함된다는 점에 주목할 가치가 있습니다. 코드베이스가 사용하는 도구에 따라 더 많은 다른 소품/속성. 내가 여기 쓴 것보다 더 복잡하다고 누군가가 말한다면 그 사람의 말을 들어보세요.
// UserIdInput.js import { useContext, createContext } from "react"; export const UserIdContext = createContext(); const UserIdInput = () => { const { userId, setUserId } = useContext(UserIdContext); const handleChange = (e) => { setUserId(e.target.value); }; return <UserIdInputComponent value={userId} onChange={handleChange} />; };
이제 이 입력은 예를 들어 사용자 편집 양식에서 재사용될 수 있습니다. 비밀번호 입력은 다음과 같습니다.
// PasswordInput.js import { useContext, createContext } from "react"; export const PasswordContext = createContext(); const PasswordInput = () => { const { password, setPassword } = useContext(PasswordContext); const handleChange = (e) => { setPassword(e.target.value); }; return ( <div> <PasswordInputComponent value={password} onChange={handleChange} /> </div> ); };
분리됨:
로직을 표현 부분인 '비즈니스 로직'과 시각적 측면에서 분리한다는 의미에서 분리됩니다. 여기서는 중간에 수정이나 새 함수 정의 없이 props가 전달되는 것을 볼 수 있습니다. 도대체 jsx도 반환하고 있습니다. 반환 키워드 없이 곧바로. 다시 말하지만, 누군가가 이보다 더 복잡하다고 말한다면... 라벨은 자체 구성요소여야 하고 입력도 마찬가지여야 합니다.
// UserIdInputComponent.js const UserIdInputComponent = ({ value, onChange }) => ( <div> <label>User Id:</label> <input type="text" value={value} onChange={onChange} required /> </div> );
// PasswordInputComponent.js const PasswordInputComponent = ({ value, onChange }) => ( <div> <label>Password:</label> <input type="password" value={value} onChange={onChange} required /> </div> );
단독:
우리는 이미 컨텍스트를 생성하여 격리된 부분을 처리했습니다. 이제 입력을 변경할 때마다 다른 입력은 다시 렌더링되지 않습니다. 다시 렌더링되는 유일한 요소는 변경되는 입력과 로그인 버튼입니다. 이는 반응 앱이 적절하게 최적화되었는지 여부를 나타내는 좋은 지표이며 때로는 조기 최적화가 좋은 경우도 있습니다. 팀의 기술을 향상시킵니다.
const LoginButton = () => { const { userId } = useContext(UserIdContext); const { password } = useContext(PasswordContext); const onClick = (e) => { e.preventDefault(); console.log("form submit", userId, password) }; return <button onClick={onClick}>Log in</button>; };
제외! 실제로는 이런 일이 발생하지 않았습니다. 변경 사항을 격리하기 위해 컨텍스트를 사용하려고 시도했지만 userId와 비밀번호를 공유하게 되었을 때 UserIdProvider를 사용하여 LoginButton을 래핑하자마자 새로운 userId와 비밀번호로 새 상태가 생성되었기 때문에 redux를 사용해야 했습니다. . 이것이 redux의 모습입니다.
// LoginButton.js import { useSelector } from "react-redux"; const LoginButton = () => { const { userId, password } = useSelector(state => state) const onClick = (e) => { e.preventDefault(); console.log("form submit", userId, password); }; return <button onClick={onClick}>Log in</button>; }; export default LoginButton
아마도 이전에 입력했어야 했는데 여기 redux 스토어가 있습니다.
// store.js import { createSlice, configureStore } from '@reduxjs/toolkit' const login = createSlice({ name: 'login', initialState: { userId: '', password: '', }, reducers: { userId: (state, action) => { state.userId = action.payload }, password: (state, action) => { state.password = action.payload } } }) export const { userId: setUserId, password: setPassword } = login.actions export const store = configureStore({ reducer: login.reducer })
redux를 사용하지만 변경 사항을 격리하여 재렌더링을 최소화하는 것이 매우 효과적입니다. 일반적으로 나는 어떤 대가를 치르더라도 재렌더링을 피하는 사람을 별로 신뢰하지 않지만 이는 좋은 반응 코드의 좋은 표시일 뿐입니다.
Here are the updated files for the two inputs. Not a lot changed but pay attention to how easy it was for me to change only the business logic component. Changed the value selector, the handleChange function and that was it. This is one of the advantages of decoupling, it’s not that obvious with such a small component but a codebase that uses complex logic I can see how this approach can be beneficial.
// UserIdInput.js (revised final) import { setUserId } from "./store"; import { useDispatch, useSelector } from "react-redux"; const UserIdInputComponent = ({ value, onChange }) => ( <div> <label>User Id:</label> <input type="text" value={value} onChange={onChange} required /> </div> ); const UserIdInput = () => { const userId = useSelector(({ userId }) => userId) const dispatch = useDispatch() const handleChange = (e) => { dispatch(setUserId(e.target.value)) }; return <UserIdInputComponent value={userId} onChange={handleChange} />; };
// PasswordInput.js (revised final) import { useDispatch, useSelector } from "react-redux"; import { setPassword } from "./store"; const PasswordInputComponent = ({ value, onChange }) => ( <> <label>Password:</label> <input type="password" value={value} onChange={onChange} required /> </> ); const PasswordInput = () => { const password = useSelector(({ password }) => password) const dispatch = useDispatch() const handleChange = e => { dispatch(setPassword(e.target.value)) }; return <PasswordInputComponent value={password} onChange={handleChange} /> };
The result should only highlight updates on the changed input and the login button itself like so:
There’s a problem though, the labels are also updating. Let’s fix that really quick just to prove the point of over, but potentially necessary optimization. Up to your discretion.
// UserIdInput.js import { setUserId } from "./store"; import { useDispatch, useSelector } from "react-redux"; const UserIdInputComponent = ({ value, onChange }) => ( <input type="text" value={value} onChange={onChange} required /> ); const UserIdInput = () => { const userId = useSelector(({ userId }) => userId) const dispatch = useDispatch() const handleChange = (e) => { dispatch(setUserId(e.target.value)) }; return <UserIdInputComponent value={userId} onChange={handleChange} />; }; // separated the label from the logic heavy component export const UserIdInputWithLabel = () => ( <div> <label>User id: </label> <UserIdInput /> </div> ) export default UserIdInputWithLabel
Here is the password input.
// PasswordInput.js import { useDispatch, useSelector } from "react-redux"; import { setPassword } from "./store"; const PasswordInputComponent = ({ value, onChange }) => ( <input type="password" value={value} onChange={onChange} required /> ); const PasswordInput = () => { const password = useSelector(({ password }) => password) const dispatch = useDispatch() const handleChange = e => { dispatch(setPassword(e.target.value)) }; return <PasswordInputComponent value={password} onChange={handleChange} /> }; // separated label from logic heavy component const PasswordInputWithLabel = () => ( <div> <label>Password: </label> <PasswordInput /> </div> ) export default PasswordInputWithLabel
This approach yields the following results:
Fully optimized.
Available here: https://github.com/redpanda-bit/reusable-decoupled-isolated
There you have it, reusable, decoupled, and isolated react components. Very small example but hope that it gives you an idea of how production grade react applications look like. It may be disappointing for some to see all the work that goes into creating a good react component, but I’ll tell ya, once you are faced with a huge form that has complex elements and possibly some animation you will see positive gains on speed. The last thing you want is an input lagging behind in front of a 100 words per minute types.
https://nextjs.org/
https://redux.js.org/
위 내용은 React: 재사용 가능, 분리 가능, 격리됨의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!