在 React.js 中正确使用 Modals(零 prop 钻取)
如果控制模态框就像编写以下效果一样简单会怎样?
const someModal = useModal()
useEffect(() => {
if (someModal.isOpen) {
setTimeout(someModal.close, 1000)
}
}, [someModal])
我的名字是 Itay Schechner,我是一名成长中的全栈开发人员,专门研究前端后端代码,尤其是 React.js。
在本文中,我将教您如何编写可读、可重用的模态实用程序。
注意:本文主要基于我之前写的一篇文章,详细解释了 Context API 的用法。
今天你将学到:
- useModal 钩子的用法
- 模态组件工厂
- 使用模态工厂编写可读代码。
模态钩子
让我们从一些 TypeScript 开始:
export interface Modal {
isOpen: boolean;
open(): void;
close(): void;
}
由此,我们了解到每个模态框都能够自行打开、关闭,并“告知”使用它的组件和钩子它是否处于打开状态。这个钩子相对容易实现:
export default function useModal(): Modal {
const [isOpen, setOpen] = useState(false);
return {
isOpen,
open() {
setOpen(true);
},
close() {
setOpen(false);
},
};
}
你可以在某个组件中使用此钩子,并大量使用 prop 语句来实现模态逻辑。例如:
export default function Navbar () {
const { isOpen, open, close } = useModal();
return (
<nav>
// ...navigation code
{ isOpen && <Modal close={close} /> }
<button onClick={open}>Open Modal</button>
</nav>
)
}
因为我们太习惯这样编写组件了,所以我们没有意识到模态框的全部潜力。如果你的模态框文件导出如下会怎么样?
import LoginModal, { LoginModalOpener } from '../auth/LoginModal';
模态工厂
与我们之前讨论过的组件工厂不同,这个工厂将更加复杂。
让我们再次从一些 TypeScript 开始,看看这个工厂的要求。
export function createModal<T extends object>(
context: Context<T>,
name: keyof T,
openerLabel: string
) { ... }
我们从中了解到了什么?
- 该函数将在提供的上下文中获取一个 Modal 类型的字段,并使用它来创建模态框
- 该函数采用 openerLabel 字段,这意味着它也将创建开启按钮。
- 如果我们提供了开场白,那么我们也应该能够提供结尾白。我希望结尾白显示一个 x 图标而不是文本,所以我会先升级上下文动作工厂。
type JSXProvider<Props> = (props: Props) => JSX.Element;
export function action<T extends object, Props extends object = {}>(
label: string | JSXProvider<Props>,
context: React.Context<T>,
consumer: (ctx: T) => void,
) {
return function ContextAction({ className, ...props }: withClass & Props) {
const ctx = useContext(context);
const action = useCallback(() => consumer(ctx), [ctx]);
return (
<button onClick={action} className={className}>
{typeof label === 'string' ? label : label(props as unknown as Props)}
</button>
);
};
}
现在,我们可以编写我们的模式工厂:
export function createModal<T extends object>(
context: Context<T>,
name: keyof T,
openerLabel: string
) {
return {
Visible: createWrapper(
context,
ctx => (ctx[name] as unknown as ModalHook).isOpen
),
Opener: action(openerLabel, context, ctx =>
(ctx[name] as unknown as Modal).open()
),
// Clear: A JSXProvider that takes width and height props
Closer: action(Clear, context, ctx =>
(ctx[name] as unknown as Modal).close()
),
};
}
让我们看看如何使用这个工厂创建简洁的代码。在我将要展示的示例中,我将在身份验证上下文中创建一个登录模式,该身份验证上下文在 App.tsx 文件中为整个应用程序提供。
// AuthContext.tsx
export default function AuthContextProvider({ children }: Wrapper) {
// other auth state ommited for bravety
const loginModal = useModal();
// effects ommitted for bravety
return (
<AuthContextProvider value={{ loginModal, ...anything }}>{ children }</AuthContextProvider>
)
}
// LoginModal.tsx
const ModalProvider = createModal(AuthContext, 'loginModal', 'Log In');
export const LoginModalOpener = ModalProvider.Opener;
export default function LoginModal() {
return (
<ModalProvider.Visible> // modal is hidden when hook state is hidden
// Modal UI (i.e dark fixed background, white modal)
<ModalProvider.Closer />
<div>
// form ommited for bravety
</div>
</ModalProvider.Visible>
)
}
// App.tsx
export default function App () {
return (
<AuthContextProvider>
<LoginModal />
<Navbar />
// rest of application
</AuthContextProvider>
)
}
现在,让我们看看我们的导航栏组件变得多么简单:
import { LoginModalOpener } from '../auth/LoginModal';
export default function Navbar () {
return (
// ... links ommited for bravety
<LoginModalOpener />
)
}
总结
如果您认为我犯了错误或者我可以写得更好,请提出建议。
我使用过这个的项目 -
文章来源:https://dev.to/itays123/using-modals-in-react-js-the-right-way-zero-prop-drilling-3ah9