自定义钩子(Custom Hooks)

定义自定义钩子

组件中与状态有关的逻辑可以通过创建自定义 Hooks 提取到函数中。

假设我们有一个组件,它订阅了一个代理(agent)并且会显示发送给它的消息。

  1. #[function_component(ShowMessages)]
  2. pub fn show_messages() -> Html {
  3. let (state, set_state) = use_state(|| vec![]);
  4. {
  5. let mut state = Rc::clone(&state);
  6. use_effect(move || {
  7. let producer = EventBus::bridge(Callback::from(move |msg| {
  8. let mut messages = (*state).clone();
  9. messages.push(msg);
  10. set_state(messages)
  11. }));
  12. || drop(producer)
  13. });
  14. }
  15. let output = state.iter().map(|it| html! { <p>{ it }</p> });
  16. html! { <div>{ for output }</div> }
  17. }

这段代码有一个问题:逻辑不能被另一个组件重用。如果我们构建另一个跟踪消息的组件,我们可以将逻辑移动到自定义钩子中,而不是复制代码。

我们将首先创建一个名为use_subscribe的新函数。 use_前缀通常表示此函数是一个钩子。这个函数将不接受任何参数并返回Rc<RefCell<Vec<String>>>

  1. fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
  2. // ...
  3. }

钩子的逻辑在use_hook的回调中。 use_hook指的是自定义 Hook 的处理函数。它接受 2 个参数: hook_runnerinitial_state_producer

hook_runner中包含了所有钩子的逻辑,它的回调的返回值又会被use_hook返回。 hook_runner需要 2 个参数:分别是对钩子和hook_callback它们两个的内部状态的可变引用。 而hook_callback同样也要 2 个参数:一个回调和一个 bool,回调接受internal_state ,也就是对内部状态实例的可变引用,并且会调执行实际的更改,还会返回表示ShouldRender的布尔值,第二个参数 bool 的用处是指示它是否在组件渲染后运行。use_hook的第二个参数initial_state_producer接受用于创建内部状态实例的回调。这里说的内部状态指的是一个实现了Hook trait 的结构体。

现在让我们为use_subscribe钩子创建状态(state struct)。

  1. /// `use_subscribe` internal state
  2. struct UseSubscribeState {
  3. /// holds all the messages received
  4. pub messages: Rc<RefCell<Vec<String>>>,
  5. }
  6. impl Hook for UseSubscribeState {}

接下来我们为use_subscribe添加实际逻辑。

  1. fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
  2. use_hook(
  3. // hook's handler. all the logic goes in here
  4. |state: &mut UseSubscribeState, hook_callback| {
  5. // calling other Hooks inside a hook
  6. use_effect(move || {
  7. let producer = EventBus::bridge(Callback::from(move |msg| {
  8. hook_callback(
  9. // where the mutations of state are performed
  10. |state| {
  11. (*state.messages).borrow_mut().deref_mut().push(msg);
  12. true // should re-render
  13. }, false // run post-render
  14. )
  15. }));
  16. || drop(producer)
  17. });
  18. // return from hook
  19. state.messages.clone()
  20. },
  21. // initial state producer
  22. || UseSubscribeState { messages: Rc::new(RefCell::new(vec![])) },
  23. )
  24. }

现在我们可以使用自定义钩子了:

  1. #[function_component(ShowMessages)]
  2. pub fn show_messages() -> Html {
  3. let state = use_subscribe();
  4. let output = state.borrow().deref().into_iter().map(|it| html! { <p>{ it }</p> });
  5. html! { <div>{ for output }</div> }
  6. }

需要特别注意的是创建自定义钩子时use_hook不是必须的,它们只是用来包含其他钩子。通常应避免使用use_hook

  1. fn use_subscribe() -> Rc<Vec<String>> {
  2. let (state, set_state) = use_state(Vec::new);
  3. use_effect(move || {
  4. let producer = EventBus::bridge(Callback::from(move |msg| {
  5. let mut messages = (*state).clone();
  6. messages.push(msg);
  7. set_state(messages)
  8. }));
  9. || drop(producer)
  10. });
  11. state
  12. }