Перерисовать функциональный компонент при изменении свойства другого класса [MVVM] - PullRequest
0 голосов
/ 03 февраля 2020

Я пытаюсь реализовать MVVM в React (требование из класса, который я беру). Я использую функциональные компоненты для представления и классы машинописи для ViewModel. Мои компоненты не перерисовываются при обновлении свойства в ViewModel.

Вот простой пример страницы, которая должна переключаться между формой входа и регистрации. SetCurrentForm вызывается правильно, и значение в ViewModel обновляется, но это не меняет представление.

// 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;
    }
  }
}

Я мог бы принудительно выполнить повторный рендеринг с помощью хука, обновив произвольное значение, но я думаю, что есть лучший способ сделать это. Что ты думаешь?

Ответы [ 2 ]

1 голос
/ 03 февраля 2020

Возможно, вы неправильно понимаете, как реагируют компоненты, которые перерисовываются, просто потому, что вы изменяете какое-то свойство в другом объекте, которое не имеет отношения к самому компоненту, даже если оно взяло свойство у этого объекта.

Хуки напрямую связаны с механизмом рендеринга реагирует и могут запускать циклы рендеринга, поэтому вы должны использовать что-то вроде этого:

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% это работает.

РЕДАКТИРОВАТЬ: это не работает, но это имеет смысл, вызов рендеринга запускается, когда вы на самом деле вызываете функцию set хука useState, не совсем понимая, как реализовать этот шаблон с хуками и без чего-то вроде 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;
0 голосов
/ 03 февраля 2020

То, что у вас здесь, не очень Реагирует . Для начала, я редко видел классы, используемые вне компонентов на основе классов. Я просто собираюсь показать здесь другое решение, которое может не совсем соответствовать тому, что вам нужно, но, надеюсь, заставит вас двигаться в правильном направлении.

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>
  )
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...