[reactjs] setState를 호출하지 않고 React 컴포넌트를 강제로 다시 렌더링 할 수 있습니까?

변경 사항을 듣고 싶은 외부 (구성 요소에 대한) 관찰 가능한 객체가 있습니다. 객체가 업데이트되면 변경 이벤트가 발생하고 변경이 감지되면 구성 요소를 다시 렌더링하려고합니다.

최상위 수준 React.render에서는 가능했지만 구성 요소 내에서는 작동하지 않습니다.render 메소드가 객체를 반환하기 ).

코드 예제는 다음과 같습니다.

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

내부적으로 버튼을 클릭하면을 호출 this.render()하지만 실제로 렌더링이 발생하는 {Math.random()}것은 아닙니다 (에 의해 작성된 텍스트 가 변경되지 않기 때문에 실제로 작동하는 것을 볼 수 있습니다). 그러나 단순히 this.setState()대신 호출하면 this.render()정상적으로 작동합니다.

내 질문은 추측 그래서 : 어떻게 반작용 구성 요소가 필요 다시 쓰게 위해 상태를 가지고? 상태를 변경하지 않고 필요에 따라 구성 요소를 업데이트하도록하는 방법이 있습니까?



답변

컴포넌트에서 전화를 걸 수 있습니다 this.forceUpdate() 하도록 .

설명서 : https://facebook.github.io/react/docs/component-api.html


답변

forceUpdateReact 사고 방식에서 벗어나므로 피해야합니다. React 문서는 언제 forceUpdate사용될 수 있는지에 대한 예를 인용합니다 .

기본적으로 구성 요소의 상태 또는 소품이 변경되면 구성 요소가 다시 렌더링됩니다. 그러나 이러한 변경이 암시 적으로 변경되는 경우 (예 : 오브젝트 자체를 변경하지 않고 오브젝트 내부의 데이터가 변경됨) render () 메소드가 다른 데이터에 의존하는 경우 React에 호출하여 render ()를 다시 실행해야한다고 알릴 수 있습니다. 강제 업데이트().

그러나 깊게 중첩 된 객체를 사용하더라도 forceUpdate불필요 하다는 아이디어를 제안하고 싶습니다 . 변경 불가능한 데이터 소스를 사용하면 변경 내용이 저렴 해집니다. 변경하면 항상 새 객체가 생성되므로 객체에 대한 참조가 변경되었는지 확인하기 만하면됩니다. Immutable JS 라이브러리를 사용하여 변경 불가능한 데이터 객체를 앱에 구현할 수 있습니다.

일반적으로 forceUpdate ()의 모든 사용을 피하고 render ()의 this.props 및 this.state에서만 읽습니다. 이렇게하면 구성 요소가 “순수”하고 응용 프로그램이 훨씬 간단하고 효율적으로 만들어집니다. 강제 업데이트()

다시 렌더링하려는 요소의 키를 변경하면 작동합니다. 상태를 통해 요소에 키 소품을 설정 한 다음 설정된 상태를 업데이트하여 새 키를 갖도록하십시오.

<Element key={this.state.key} /> 

그런 다음 변경이 발생하고 키를 재설정합니다

this.setState({ key: Math.random() });

이것이 키가 바뀌는 요소를 대체한다는 점에 유의하고 싶습니다. 이것이 유용한 위치의 예는 이미지 업로드 후 재설정 할 파일 입력 필드가있는 경우입니다.

OP의 질문에 대한 진정한 대답 forceUpdate()은이 솔루션이 다른 상황에서 유용하다는 것을 알았습니다. 또한 당신이 자신을 사용하는 것을 발견 forceUpdate하면 코드를 검토하고 다른 방법이 있는지 확인할 수 있습니다.

참고 1-9-2019 :

위의 (키 변경) 요소를 완전히 대체합니다. 변경 사항을 적용하기 위해 키를 업데이트하면 코드 어딘가에 문제가있을 수 있습니다. Math.random()in 키를 사용하면 각 렌더마다 요소를 다시 만듭니다. 반응은 키를 사용하여 물건을 다시 렌더링하는 가장 좋은 방법을 결정하므로 키를 업데이트하지 않는 것이 좋습니다.


답변

실제로 추가 로직이 구현 되거나 단순히 반환 될 때 다시 렌더링을 트리거 하지 않을forceUpdate()있는 유일한 올바른 솔루션 입니다.setState()shouldComponentUpdate()false

강제 업데이트()

호출 forceUpdate()하면 render()구성 요소에서을 (를) 건너 뛰게 shouldComponentUpdate()됩니다. 더…

setState ()

setState()에 조건부 렌더링 로직이 구현되지 않는 한 항상 다시 렌더링을 트리거합니다 shouldComponentUpdate(). 더…


forceUpdate() 구성 요소 내에서 this.forceUpdate()


후크 : React에서 후크로 컴포넌트를 강제로 다시 렌더링하려면 어떻게해야합니까?


BTW : 상태를 변경하거나 중첩 된 속성이 전파되지 않습니까?


답변

나는 forceUpdate다음을 수행함으로써 피했다.

잘못된 방법 : 인덱스를 키로 사용하지 마십시오

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

올바른 방법 : 데이터 ID를 키로 사용하십시오.

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

이러한 코드 개선을 수행하면 구성 요소가 고유 하고 자연스럽게 렌더링됩니다.


답변

관계 (부모-자식)에 의해 구속되지 않는 두 개의 React 구성 요소가 통신하도록하려면 Flux 또는 유사한 아키텍처 를 사용하는 것이 좋습니다 .

모델과 인터페이스를 보유하는 관찰 가능 구성 요소 저장소의 변경 사항을 수신 하고 렌더가 다음과 같이 변경되도록하는 데이터를 저장하는 것이 좋습니다.state .MyComponent . 저장소가 새 데이터를 푸시하면 구성 요소의 상태가 변경되어 렌더링이 자동으로 트리거됩니다.

일반적으로 사용을 피하도록 노력해야합니다 forceUpdate() . 설명서에서 :

일반적으로 forceUpdate ()의 모든 사용을 피하고 render ()의 this.props 및 this.state에서만 읽습니다. 따라서 응용 프로그램이 훨씬 간단하고 효율적으로 만들어집니다


답변

후크를 사용하거나 HOC를 선택하십시오

후크 사용 또는 HOC (고차 구성 요소) 패턴을 당신의 상점이 변경 될 때, 당신은 자동 업데이트를 할 수 있습니다. 이것은 프레임 워크가없는 매우 가벼운 접근법입니다.

useStore상점 업데이트를 처리하는 Hooks 방법

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withstores HOC 핸들 스토어 업데이트

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

SimpleStore 클래스

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

사용하는 방법

후크의 경우 :

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

HOC의 경우 :

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

SURE 만들
그래서 같은 글로벌 변화의 혜택을 얻을 수있는 싱글로 저장을 내보내려면 :

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

이 방법은 매우 간단하며 저에게 효과적입니다. 나는 또한 큰 팀에서 일하고 Redux와 MobX를 사용하고 좋은 것이지만 많은 상용구를 찾습니다. 나는 항상 필요할 때 간단 할 수있는 많은 코드를 싫어했기 때문에 개인적으로 내 접근법을 좋아합니다.


답변

그래서 내 질문은 : React 구성 요소를 다시 렌더링하기 위해 상태를 가져야합니까? 상태를 변경하지 않고 필요에 따라 구성 요소를 업데이트하도록하는 방법이 있습니까?

다른 답변은 당신이 할 수있는 방법을 설명하려고 노력했지만 요점은 당신이해서는 안된다는 것입니다 입니다. 열쇠를 바꾸는 해키 솔루션조차도 요점을 놓치고 있습니다. React의 힘은 무언가를 렌더링해야 할 때 수동으로 관리하는 것을 포기하고 대신 입력에 무언가를 매핑하는 방법과 관련이 있습니다. 그런 다음 입력 스트림을 제공하십시오.

재 렌더링을 수동으로 강제해야하는 경우, 올바른 일을하지 않는 것이 거의 확실합니다.