在处理新页面时,不要只是开始在页面本身上编写组件。开始个性化组件单元,以便每个组件都足够独立,并且全局状态的更改不会导致重新渲染。例如,假设您被赋予创建此页面的任务。
您可能会像我学习编码时一样,将所有这些组件写入一个文件中。
// 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} /> }
这使得故事书等工具能够通过仅导出与状态管理分离的表示组件来正常工作。必须在 Storybook 上集成逻辑繁重的组件(进行 api 调用、进行全局状态更改)可能会很烦人。通过这种方法,您可以看到组件如何根据不同的 props 在视觉上发生变化。
返回主页。你大概可以明白我的意思了。而不是将所有内容都写在同一页上。想想如何重用该组件,如何将其与状态解耦以及如何将其隔离,以便它永远不会重新渲染,除非与该组件相关的 props 发生变化。
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 等。
现在包含一些逻辑的部分,即登录表单。不确定是登录、登录还是登录,但我们将继续使用 Facebook 的术语“登录”。
提醒一下,每个组件都应该可重用、解耦和隔离。
可重复使用:
首先让 UserIdInput 可重用,然后将此方法复制到其他密码输入上:值得注意的是,生产级别输入将包括其他属性,例如测试 id、根据 props 更改的类、aria 属性、自动对焦等等更多其他道具/属性取决于代码库使用的工具。如果有人告诉您这比我在这里写的更复杂,请听那个人的意见。
// 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直接没有 return 关键字。再说一次,如果有人告诉你它比这更复杂,那是……标签应该是它们自己的组件,输入也是。
// 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> );
隔离:
我们已经通过创建上下文来处理隔离部分,现在每当我们更改任何输入时,其他输入都不会重新渲染。唯一将重新呈现的元素是正在更改的输入和登录按钮。这是一个很好的指标,表明你的 React 应用程序是否得到了适当的优化,过早的优化有时是好的。提升团队技能。
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 和密码时,我必须使用 redux,因为一旦我使用 UserIdProvider 来包装 LoginButton,它就会使用新的 userId 和密码创建一个新状态。这就是 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中文网其他相关文章!