我有一个外部(组件),可观察对象,我想监听的变化。当对象更新时,它会发出更改事件,然后我希望在检测到任何更改时重新呈现组件。

使用顶级React。渲染这是可能的,但在组件中它不起作用(这是有意义的,因为渲染方法只返回一个对象)。

下面是一个代码示例:

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(),它就可以正常工作。

所以我想我的问题是:React组件需要有状态才能渲染吗?是否有一种方法可以在不改变状态的情况下强制组件按需更新?


当前回答

我通过执行以下操作避免了forceUpdate

错误方法:不使用索引作为键

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

正确方法:使用数据id作为键,它可以是一些guid等

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

所以通过这样的代码改进,你的组件将是唯一的,呈现自然

其他回答

实际上,forceUpdate()是唯一正确的解决方案,因为如果在shouldComponentUpdate()中实现了额外的逻辑,或者当它只是返回false时,setState()可能不会触发重新呈现。

强制更新()

调用forceUpdate()将导致在组件上调用render(),跳过shouldComponentUpdate()。更多…

设置状态()

setState()总是会触发重新呈现,除非在shouldComponentUpdate()中实现了条件呈现逻辑。更多…

forceUpdate()可以通过this.forceUpdate()从组件中调用

钩子:如何在React中使用钩子强制组件重新渲染?

顺便说一句:你是在改变状态还是你的嵌套属性不传播?

如何在React中更新嵌套的状态属性 沙盒

所以我想我的问题是:React组件需要有状态吗 要重新渲染?是否有一种方法强制组件更新 不改变状态的需求?

其他答案试图说明你可以这样做,但关键是你不应该这样做。即使是改变密钥这种俗气的解决方案也没有抓住重点。React的强大之处在于放弃手动管理内容何时应该呈现的控制,而只是关注内容应该如何映射到输入上。然后供给输入流。

如果您需要手动强制重新渲染,那么几乎可以肯定您做得不对。

使用钩子或HOC选择

使用钩子或HOC(高阶组件)模式,可以在存储发生变化时进行自动更新。这是一种没有框架的轻量级方法。

useStore钩子处理存储更新的方式

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);

确保 以单例方式导出你的商店,以获得全局变化的好处,如下所示:

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,并发现它们也很好,但只是很多样板文件。我只是个人喜欢我自己的方法,因为我总是讨厌大量的代码,当你需要的时候,它可以是简单的。

ES6 -我包括一个例子,这对我很有帮助:

在一个“简短的if语句”中,你可以像这样传递一个空函数:

isReady ? ()=>{} : onClick

这似乎是最短的方法。

()=>{}

为了完成你所描述的,请尝试this.forceUpdate()。