useEffect 的 SideEffect

SideEffect 在函数式编程的概念里是对 impure function 造成影响的一种描述,而相对的另外一种没有 SideEffect 的函数就叫pure function。比如下面的两个例子中:

function add(a, b) { return a + b;}
function addRondom(a) { return a + Math.floor(Math.random()*10)}

add 是一个 pure function ,因为只要它的输入是相同的那么你可以就保证它永远可以得到相同的值。而 addRondom 就如同函数名一样,相同的输入并不能保证得到相同的输出,因为我们无法确保 Math.floor(Math.random()*10 每次都会返回同样的值。

下面的例子中就是在 React 的组件中比较常见的 SideEffect :

class C extends React.Component{
  constructor() {
    super();
    this.state = {count: 0}
  }
  onClicked = () => {
    // SideEffect to update the state
    this.setState((s) => {...s, count: s.count + 1})
  }
  render() {
    return (<div onClick={this.onClicked}>{this.state.count}</div>)
  }
}

这个例子中,我们监听点击事件然后不断的将 count+1 ,每次的点击对组件 C 来说产生一次 SideEffect 这时候我们就会重新渲染这个组件和他的所有子组件。

除了用户点击事件外,我们可能会需要在组件加载后获取远程数据然后渲染,比如:

class C React.Component {
  constuctor(){
    super();
    this.state = {count: 0}
  }
  effect = async (url) => {
    fetch(url)
      .then(res => res.json())
      .then(d => this.setState({count: d.count}))
  }
  componentDidMount() {
	effect(this.props.url)
  }
  componentDidUpdate(preProps) {
    if (this.props.url != preProps.url) {
      effect(this.props.url)
    }
  }
  render() {
    return (<div>{this.state.count}</div>)
  }  
}

上面的例子中,组件 C 会在第一次加载时获取远程数据然后设置 state 这时组件也会重新再次渲染,而 componentDidUpdate 内的函数确保我们的组件在远程地址变更后也会重新再次获取最新数据然后再次重新渲染。

当我们在使用函数式组件( functional component )的时候可能需要引入一个高阶组件(HOC)来处理这些 SideEffect,所以在 React 16.8 后的版本引入了 useEffect 这个hook。上面的例子可以很容易的改写成:

const C = (props) => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(d => setCount(d.count)
  }, [props.url])
  return (<div>{count}</div>)
}