【问题标题】:Re-render functional component when property of another class changes [MVVM]当另一个类的属性发生变化时重新渲染功能组件[MVVM]
【发布时间】:2020-02-03 15:01:47
【问题描述】:

我正在尝试在 React 中实现 MVVM(我正在学习的课程的要求)。我正在为视图使用功能组件,并为 ViewModel 提供打字稿类。但是,当 ViewModel 中的属性更新时,我的组件不会重新渲染。

这是一个应该在登录和注册表单之间切换的页面的简单示例。 setCurrentForm 被正确调用并且 ViewModel 中的值会更新,但不会更改 View。

// AuthView.tsx
const AuthView: React.FC = () => {
  const VM = new AuthViewModel();

  let form;
  if (VM.currentForm === FORMS.SignUp) {
    // Toggles the current form between FORMS.SignUp and FORMS.Login
    form = <SignUpForm setCurrentForm={() => VM.setCurrentForm()} />
  } else {
    form = <LoginForm setCurrentForm={() => VM.setCurrentForm()} />
  }

  return (
    <Container>
      {/* Sign up card */}
      <div className="mt-12">
        {form}
      </div>
    </Container> 
  );
}
export default AuthView;
// AuthViewModel.tsx
export default class AuthViewModel {
  email: string = "";
  password: string = "";
  currentForm: FORMS = FORMS.SignUp;

  setCurrentForm() {
    console.log("Setting form in VM");
    if (this.currentForm === FORMS.SignUp) {
      console.log("Switching to login")
      this.currentForm = FORMS.Login;
    } else if (this.currentForm === FORMS.Login) {
      console.log("Switching to signup")
      this.currentForm = FORMS.SignUp;
    }
  }
}

我可以通过更新任意值来强制使用钩子重新渲染,但我认为有更好的方法来做到这一点。你怎么认为?

【问题讨论】:

    标签: reactjs typescript mvvm


    【解决方案1】:

    您可能误解了 react 组件如何重新渲染,只是因为您更改了另一个对象中的某些属性,它与组件本身无关,即使它已经从该对象中获取了属性。

    Hooks 直接连接到 reacts 渲染机制并且可以触发渲染周期,因此你应该使用这样的东西:

    const AuthView: React.FC = () => {
      // if you don't put this in a state a new VM will be created when the component rerenders
      const [VM] = useState(new AuthViewModel());
    
      useEffect(() => {
        // Maybe some handler code is needed?
      }, VM.currentForm);
    
      let form;
      if (VM.currentForm === FORMS.SignUp) {
        // Toggles the current form between FORMS.SignUp and FORMS.Login
        form = <SignUpForm setCurrentForm={() => VM.setCurrentForm()} />
      } else {
        form = <LoginForm setCurrentForm={() => VM.setCurrentForm()} />
      }
    
      return (
        <Container>
          {/* Sign up card */}
          <div className="mt-12">
            {form}
          </div>
        </Container> 
      );
    }
    export default AuthView;
    

    我从未尝试通过钩子观察嵌套属性,所以这不是 100% 有效的。

    编辑:它不起作用,但它是有道理的,当你实际调用 useState 钩子的 set 函数时,渲染调用会被触发,不太确定如何使用钩子实现这种模式,而不需要像 redux 或 mobx 这样的东西,但这是我最好的方法:

    class AuthViewModel() {
    
      constructor(public readonly currentForm = 'LOGIN');
    
      public setCurrentForm = () => {
        if(this.currentForm === 'LOGIN')
          return new AuthViewModel('SIGNUP')
        else
          return new AuthViewModel(); // will default to login
      }
    }
    

    然后是组件

    const AuthView: React.FC = () => {
      // if you don't put this in a state a new VM will be created when the component rerenders
      const [VM, setVM] = useState(new AuthViewModel());
    
      let form;
      if (VM.currentForm === FORMS.SignUp) {
        // Toggles the current form between FORMS.SignUp and FORMS.Login
        form = <SignUpForm setCurrentForm={() => setVM(VM.setCurrentForm())} />
      } else {
        form = <LoginForm setCurrentForm={() => setVM(VM.setCurrentForm())} />
      }
    
      return (
        <Container>
          {/* Sign up card */}
          <div className="mt-12">
            {form}
          </div>
        </Container> 
      );
    }
    
    export default AuthView;
    

    【讨论】:

    • 感谢您的帮助,遗憾的是这不起作用。我知道通常更新组件中的状态会更新组件(使用 setState 或 useState)。
    • 更新了答案 :) 不确定这是否是您想要的模式,基本上 VM 是一个浅对象,以便挂钩到渲染周期,您可能需要查看 redux 或 mobx 和找出一种方法来告诉 react 在其状态更改时重新渲染组件
    • 这是有道理的。我在想我需要抓住像 mobx 这样的东西来让视图观察视图模型。谢谢!
    • 如果你觉得答案有用,别忘了点赞 :)
    【解决方案2】:

    你在这里的感觉不是很反应。对于初学者,我很少看到在基于类的组件之外使用的类。我只是在这里吐出一个不同的解决方案,它可能不完全符合你的需要,但希望能让你朝着正确的方向前进。

    const Authenticate: FC = props => {
      const [mode, setMode] = useState<"login" | "create">("login");
    
      return (
        <div>
          {mode === "login" && <Login onLogin={({email, password}) => {/*login handler logic*/}}/>}
          {mode === "create" && <CreateAccount onCreate={({email, password}) => {/*create handler logic*/}}/>}
    
          <button 
            disabled={mode === "login"} 
            onClick={() => setMode("login")}
          >
            login
          </button>
          <button 
            disabled={mode === "create"} 
            onClick={() => setMode("create")}
          >
            sign up
          </button>
        </div>
      )
    }
    
    const Login: FC<{onLogin: ({email: string, password: string}) => any}> = props => {
      const [email, setEmail] = useState("");
      const [password, setPassword] = useState("");
      const { onLogin } = props;
    
      return (
        <form onSubmit={() => onLogin({email, password})}>
          <input value={email} onChange={e => setEmail(e.target.value)} />
          <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
          <button type="submit">Login</button>
        </form>
      );
    }
    
    const CreateAccount: FC<{onCreate: ({email: string, password: string}) => any}> = props => {
      return (
        <div>... similar to <Login/> ... </div>
      )
    }
    

    【讨论】:

    • 我知道这不是很像反应。我通常不会构建这样的应用程序。我会使用钩子并在实际组件中保持状态和登录,但我必须对我正在学习的类使用 MVVM 或 MVP,这需要将大部分逻辑移出我的功能组件并让它们依赖于 ViewModel 类.
    • 这是一个设计模式类。默认选项是在 Android 中执行此操作。我在 React 工作,所以我认为这会为我节省很多时间,但是 TA(React 经验有限)给选择不使用 Android 的学生增加了越来越多的限制。
    猜你喜欢
    • 2021-06-12
    • 2021-01-13
    • 1970-01-01
    • 2020-02-27
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 2022-06-30
    • 2021-07-09
    相关资源
    最近更新 更多