Skip to content

React-Hoos ​

官方文档

hooks 简介 ​

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook 就是一个函数,它可以返回状态及更改状态的方法。

jsx
import React, { useState } from 'react';

function Example () {
  const [count, setCount] = useState(0);
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button
      	onClick={ () => setCount(count + 1) }  
      >
      	Click
      </button>
    </div>
  );
}
import React, { useState } from 'react';

function Example () {
  const [count, setCount] = useState(0);
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button
      	onClick={ () => setCount(count + 1) }  
      >
      	Click
      </button>
    </div>
  );
}

无破坏性改动 ​

hook 是 100% 向后兼容的,是可选的,React 没有计划移除 class。

Hook 不会影响你对 React 概念的理解。Hook 为已知的 React 概念提供了更直接的 API。

动机 ​

Hook 主要是用来解决传统类组件中存在的问题。

  • 组件之间复用状态逻辑很难;
  • 复杂组件变得难以理解;
  • 难以理解的 class(this 指向);

Hook 规则 ​

Hook 本质就是 JavaScript 函数,但是在使用时需要遵循两条规则。可以使用 linter 插件来强制执行这些规则。

只在最顶层使用 Hook ​

不要在循环时,条件或嵌套函数中调用 Hook。

不允许出现顺序颠倒的情况。

只在 React 函数中调用 Hook ​

不要在普通的 JavaScript 函数中调用 Hook。你可以这样做:

  • 在 React 的函数组件中调用 Hook
  • 在自定义 Hook 中调用其他 Hook

State Hook ​

Hook 指在不编写类的情况下来写组件。

hook 与 类写法对比 ​

hook 写法:

js
const { useState } = React;

function App () {
  const [count, setCount] = useState(0);
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button
      	onClick={ () => setCount(count + 1) }  
      >
      	Click me
      </button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('app'));
const { useState } = React;

function App () {
  const [count, setCount] = useState(0);
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button
      	onClick={ () => setCount(count + 1) }  
      >
      	Click me
      </button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('app'));

类写法:

js
const { Component } = React;

class App extends Component {
  state = {
    count: 0
  }

  render () {
    const { count } = this.state;

    return (
      <div>
        <p>You Clicked { count } times</p>
        <button
          onClick={ () => this.setState({ count: count + 1 }) }  
        >
          Click me
        </button>
      </div>
    )
  }
};

ReactDOM.render(<App />, document.getElementById('app'));
const { Component } = React;

class App extends Component {
  state = {
    count: 0
  }

  render () {
    const { count } = this.state;

    return (
      <div>
        <p>You Clicked { count } times</p>
        <button
          onClick={ () => this.setState({ count: count + 1 }) }  
        >
          Click me
        </button>
      </div>
    )
  }
};

ReactDOM.render(<App />, document.getElementById('app'));

Hook 和函数组件 ​

jsx
const App = (props) => {
  return <div />;
}
const App = (props) => {
  return <div />;
}
jsx
function App (props) {
  return <div />;
}
function App (props) {
  return <div />;
}

你之前可能把它们叫做 ”无状态组件“(木偶组件、呆组件),当我们为它们引入 React state 能力后,可以叫做 ”函数组件“。

Hook 在 class 内部是不起作用的,但是可以使用它们取代 class。

Hook 是什么 ​

Hook 是一个特殊函数,它可以让你 ”钩入“ React 的特性。例如 useState 是允许你在 React 函数组件中添加 state 的 Hook。

如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转换为 class,现在就可以在函数组件中使用 Hook。

声明 State 变量 ​

jsx
class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}
class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}
js
import React, { useState } from 'react';

function App () {
  const [count, setCount] = useState(0);
}
import React, { useState } from 'react';

function App () {
  const [count, setCount] = useState(0);
}

读取 State ​

class:

jsx
<p>You clicked { this.state.count } times</p>
<p>You clicked { this.state.count } times</p>

函数:

js
<p>You clicked { count } times</p>
<p>You clicked { count } times</p>

更新 State ​

class:

jsx
<button
	onClick={() => this.setState({ count: this.state.count + 1 })}  
>
	Click me
</button>
<button
	onClick={() => this.setState({ count: this.state.count + 1 })}  
>
	Click me
</button>

函数:

jsx
<button
	onClick={() => setCount(count + 1 )}  
>
	Click me
</button>
<button
	onClick={() => setCount(count + 1 )}  
>
	Click me
</button>

使用多个 state 变量 ​

jsx
const { useState } = React;

function App () {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  console.log('render');
  
  return (
  	<div>
    	<p>You Clicked { count1 } times</p>
      <button onClick={ () => setCount1(count1 + 1) }>Click me</button>
    </div>
  );
};
const { useState } = React;

function App () {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  console.log('render');
  
  return (
  	<div>
    	<p>You Clicked { count1 } times</p>
      <button onClick={ () => setCount1(count1 + 1) }>Click me</button>
    </div>
  );
};

初次渲染(app 挂载时),执行 render。点击时,也执行 render。意味着 App 重新执行一遍。

js
function App () {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  console.log('render');
  
  return (
  	<div>
    	<p>You Clicked { count1 } times</p>
    	<p>You Clicked { count2 } times</p>
    	<p>You Clicked { count3 } times</p>
      <button onClick={ () => setCount1(count1 + 1) }>Click me</button>
      <button onClick={ () => setCount2(count2 + 1) }>Click me</button>
      <button onClick={ () => setCount3(count3 + 1) }>Click me</button>
    </div>
  );
};
function App () {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  console.log('render');
  
  return (
  	<div>
    	<p>You Clicked { count1 } times</p>
    	<p>You Clicked { count2 } times</p>
    	<p>You Clicked { count3 } times</p>
      <button onClick={ () => setCount1(count1 + 1) }>Click me</button>
      <button onClick={ () => setCount2(count2 + 1) }>Click me</button>
      <button onClick={ () => setCount3(count3 + 1) }>Click me</button>
    </div>
  );
};

运行同一个钩子,返回不同的结果,而且不同的结果在运行时依旧可以保持顺序的一致性。

记忆单元格(状态数组,状态列表)。

如果使用循环、条件、嵌套函数中调用 Hook,会报错(hook is not defined)。

state hook 重点 ​

js
const [count, setCount] = useState(0);
const [count, setCount] = useState(0);

1. useState 返回的 setCount 函数无论 render 多少次,都是一个函数(唯一变量,优化内存空间)。

count 变量不是同一个变量。

useState 返回的数组第二个参数引用是一致的。

2. 使用 setCount 赋值相同的值,函数组件不会渲染。类组件(父组件)对于修改相同值,会重新渲染。

React 对于变量的比较方式是 Object.is。

3. 函数更新问题。

函数更新(上一次的 state,更新的最新的 state)和不同的返回值更新。

js
const [count, setCount] = useState(0);

const onClick = params => {
  setCount(count + 1);
  console.log(count); // 0
  // 内部采用闭包的方式实现类似异步的效果。
}

console.log(count); // 1
const [count, setCount] = useState(0);

const onClick = params => {
  setCount(count + 1);
  console.log(count); // 0
  // 内部采用闭包的方式实现类似异步的效果。
}

console.log(count); // 1
js
const [count, setCount] = useState(0);

const onClick = params => {
  setCount(count + 1);
  setCount(count + 1);
  // 触发 click 事件后,render 时 count 为 1,两次都是拿到旧的值
}


const onClick = params => {
  setCount(count => count + 1);
  setCount(count => count + 1);
  // 使用箭头函数可以解决上述问题
}
const [count, setCount] = useState(0);

const onClick = params => {
  setCount(count + 1);
  setCount(count + 1);
  // 触发 click 事件后,render 时 count 为 1,两次都是拿到旧的值
}


const onClick = params => {
  setCount(count => count + 1);
  setCount(count => count + 1);
  // 使用箭头函数可以解决上述问题
}

4. 多次赋值渲染时会合并处理。性能优化的手段。

js
const onClick = params => {
  setCount(count + 1);
  setCount(count + 1);
}
const onClick = params => {
  setCount(count + 1);
  setCount(count + 1);
}

5. 类组件设置 state 状态会合并,函数组件中不会合并。

jsx
function App () {
  const [counter, setCount] = useState({ count: 0 });
  
  console.log('render');
  
  const onClick = params => {
    setCount({
      count: counter.count + 1
    });
  }
  
  return (
 		<div>
      <p>{ counter.count }</p>
      <div>
        <button onClick={ onClick }>Click me</button>
      </div>
    </div>
  )
}
function App () {
  const [counter, setCount] = useState({ count: 0 });
  
  console.log('render');
  
  const onClick = params => {
    setCount({
      count: counter.count + 1
    });
  }
  
  return (
 		<div>
      <p>{ counter.count }</p>
      <div>
        <button onClick={ onClick }>Click me</button>
      </div>
    </div>
  )
}
js
function App () {
  const [counter, setCount] = useState({ count: 0 });
  
  console.log('render');
  
  const onClick = params => {
    setCount({
      count1: counter.count + 1
    });
    // setCount 时传入新对象,会以新对象为主,不会合并对象。
  }
  
  return (
 		<div>
      <p>{ counter.count }</p>
      <div>
        <button onClick={ onClick }>Click me</button>
      </div>
    </div>
  )
}
function App () {
  const [counter, setCount] = useState({ count: 0 });
  
  console.log('render');
  
  const onClick = params => {
    setCount({
      count1: counter.count + 1
    });
    // setCount 时传入新对象,会以新对象为主,不会合并对象。
  }
  
  return (
 		<div>
      <p>{ counter.count }</p>
      <div>
        <button onClick={ onClick }>Click me</button>
      </div>
    </div>
  )
}

6. 强制刷新组件

jsx
// 类组件

class App extends Component {
  state = {
    count: 0
  }

  shouldComponentUpdate () {
    console.log('before update');

    return true;
  }
  
  render () {
  	console.log('render');
    const { count } = this.state;

  	return (
      <div>
        <button onClick={ () => {
        	console.log(1);
        	this.forceUpdate();
          // this.setState({
          //   count: count + 1
          // })
      	} }>Click me</button>
      </div>
    )
  }
}

// 状态没有更新,不会触发 render。
// 可以使用 this.forceUpdate() 方法进行强制刷新;
// 强制刷新不会触发 shouldComponentUpdate 方法。
// 类组件

class App extends Component {
  state = {
    count: 0
  }

  shouldComponentUpdate () {
    console.log('before update');

    return true;
  }
  
  render () {
  	console.log('render');
    const { count } = this.state;

  	return (
      <div>
        <button onClick={ () => {
        	console.log(1);
        	this.forceUpdate();
          // this.setState({
          //   count: count + 1
          // })
      	} }>Click me</button>
      </div>
    )
  }
}

// 状态没有更新,不会触发 render。
// 可以使用 this.forceUpdate() 方法进行强制刷新;
// 强制刷新不会触发 shouldComponentUpdate 方法。
jsx
// 函数组件

function App () {
  const [count, setCount] = useState(0);
  const [, _setCount] = useState({});

  console.log('render');

  const onClick = params => {
    _setCount({});
  }
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button onClick={ onClick }>Click me</button>
    </div>
  );
};

// 函数组件可以定义内部的方法,使用方法赋值为空对象,进行强制刷新。
// 函数组件

function App () {
  const [count, setCount] = useState(0);
  const [, _setCount] = useState({});

  console.log('render');

  const onClick = params => {
    _setCount({});
  }
  
  return (
  	<div>
    	<p>You Clicked { count } times</p>
      <button onClick={ onClick }>Click me</button>
    </div>
  );
};

// 函数组件可以定义内部的方法,使用方法赋值为空对象,进行强制刷新。

7. 所有 hook 不能在 if、switch、for 中使用

8. 只能在组件中使用 hook (可以在自定义组件)

9. 所有的 hook 必须定义在最开始位置,方便阅读

10. 惰性初始化 state

js
const [count, setCount] = useState(() => {
  return 0;
});
const [count, setCount] = useState(() => {
  return 0;
});

Effect Hook ​

副作用 ​

Effect Hook 可以在函数组件中执行副作用操作。

纯函数只要和外部存在交互,就存在副作用。

  1. 引用外部变量;
  2. 调用外部函数;

相同的输入一定会引起相同的输出(纯函数)。

react 中,只要不是在组件渲染使用到的值,都是副作用。

副作用:修改 DOM,修改全局变量,ajax 等,计时器,存储相关。

副作用对比 ​

hooks

js
const [count, setCount] = useState();

useEffect(() => {
  document.title = `You clicked ${count} times`;
});
const [count, setCount] = useState();

useEffect(() => {
  document.title = `You clicked ${count} times`;
});

class

js
componentDidMount () { }

componentDidUpdate () { }
componentDidMount () { }

componentDidUpdate () { }

hooks 中的 effect hook 相当于把 componentDidMount 和 componentDidUpdate 进行简单的合并。

执行时机 ​

js
function App () {
  console.log('render');
  
  useEffect(() => {
    console.log('effect');
  });
}
function App () {
  console.log('render');
  
  useEffect(() => {
    console.log('effect');
  });
}

初次渲染之后,执行 useEffect。数据更新完成后,也会执行 useEffect。

effect 在真实 DOM 的完成渲染执行。

componentDidMount 、componentDidUpdate 在真实 DOM 渲染之前执行。

effect hook 中的钩子执行是异步的。

effect 作用 ​

告诉 React 组件需要在渲染后执行某些操作。React 会保存传递的函数,在 DOM 更新之后调用它。

在组件内部使用 effect 可以获取 state 变量。

useEffect 默认在第一次渲染之后和每次更新之后都会执行。React 保证每次运行 effect 时,DOM 都已经更新完毕。

componentDidMount 、componentDidUpdate 会阻塞页面执行,effect 不会阻塞浏览器更新屏幕。

如果想在 DOM 更新之后使用 effect,需要用到 useLayoutEffect Hook API。

effect 清除 ​

js
useEffect(() => {
  let timer = setInterval(() => {
    setCount(count + 1);
  });
  
  return () => {
    
  };
});
useEffect(() => {
  let timer = setInterval(() => {
    setCount(count + 1);
  });
  
  return () => {
    
  };
});

render 时,先执行清理函数,然后执行 render。

js
render、useEffect -> render + 清理函数 + useEffect
									-> render + 清理函数 + useEffect
render、useEffect -> render + 清理函数 + useEffect
									-> render + 清理函数 + useEffect

存在清理函数时,清理函数在每一次运行副作用函数之前执行。

每一次执行的副作用函数都是不同的函数,和上次的函数没有任何关系。

清理函数在组件销毁(移除)的时候也会执行。

effect 第二个参数 ​

指定当前 effect 函数所需要的依赖项。

如果依赖是空数组,只会在挂载(初次渲染)和卸载的时候执行。

如果存在依赖项并且依赖项不一致会重新执行 effect。

多个 effect 实现关注点分离 ​

jsx
useEffect(() => {
  document.title = 'You clicked ${count} times';
  console.log('effect');
})
useEffect(() => {
  document.title = 'You clicked ${count} times';
  console.log('effect');
})

=>

js
useEffect(() => {
  document.title = 'You clicked ${count} times';
});

useEffect(() => {
  console.log('effect');
});
useEffect(() => {
  document.title = 'You clicked ${count} times';
});

useEffect(() => {
  console.log('effect');
});

只需要保证 effect 的调用顺序,就可以实现关注点分离。

=>

js
// 自定义 hook

const useCount = params => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = 'You clicked ${count} times';
  });
  
  return [count, setCount];
}

const Foo = params => {
  const [count, setCount] = useCount();
  
  console.log('render');
  
  useEffect(() => {
    console.log('effect');
  });
}
// 自定义 hook

const useCount = params => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = 'You clicked ${count} times';
  });
  
  return [count, setCount];
}

const Foo = params => {
  const [count, setCount] = useCount();
  
  console.log('render');
  
  useEffect(() => {
    console.log('effect');
  });
}

竞态问题 ​

jsx
const API = {
  async queryEmployeesById (id) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ id, currentDeparment: `currentDeparment: ${ id }` })
      }, 1000 / (5 - id));
    });
  }
};

const Deparment = props => {
  const [id] = props;
  const [employees, setEmployees] = useState({});
  
  useEffect(() => {
    (async function fetchDate () {
      const employees = await API.queryEmployeesById(id);
      setEmployees(employees);
    })();
  }, [id]);
  
  return (
  	<div>
    	<p>{ employees.currentDeparment }</p>
    </div>
  );
}

const App = params => {
  const [id, setId] = useState(1);
  
  return (
   <div>
   	<div>id: { id }</div>
    <Deparment id={ id }></Deparment>
    <button onClick={() => setId(id + 1)}>增加 ID</button>
   </div>
  );
}
const API = {
  async queryEmployeesById (id) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ id, currentDeparment: `currentDeparment: ${ id }` })
      }, 1000 / (5 - id));
    });
  }
};

const Deparment = props => {
  const [id] = props;
  const [employees, setEmployees] = useState({});
  
  useEffect(() => {
    (async function fetchDate () {
      const employees = await API.queryEmployeesById(id);
      setEmployees(employees);
    })();
  }, [id]);
  
  return (
  	<div>
    	<p>{ employees.currentDeparment }</p>
    </div>
  );
}

const App = params => {
  const [id, setId] = useState(1);
  
  return (
   <div>
   	<div>id: { id }</div>
    <Deparment id={ id }></Deparment>
    <button onClick={() => setId(id + 1)}>增加 ID</button>
   </div>
  );
}

可以在请求前给 effect 标识。

js
const Deparment = props => {
  const [id] = props;
  const [employees, setEmployees] = useState({});
  
  useEffect(() => {
    let didCancel = false;
    
    (async function fetchDate () {
      const employees = await API.queryEmployeesById(id);
      
      if (!didCancel) {
        setEmployees(employees);
      }
    })();
    
    return () => {
      didCancel = true;
    };
  }, [id]);
  
  return (
  	<div>
    	<p>{ employees.currentDeparment }</p>
    </div>
  );
}
const Deparment = props => {
  const [id] = props;
  const [employees, setEmployees] = useState({});
  
  useEffect(() => {
    let didCancel = false;
    
    (async function fetchDate () {
      const employees = await API.queryEmployeesById(id);
      
      if (!didCancel) {
        setEmployees(employees);
      }
    })();
    
    return () => {
      didCancel = true;
    };
  }, [id]);
  
  return (
  	<div>
    	<p>{ employees.currentDeparment }</p>
    </div>
  );
}

自定义 Hook ​

案例代码 1 ​

jsx
class App extends React.Component {
  state = {
    info: []
  };

  componentDidMount () {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      this.setState({
        info: res
      });
    })();
  }

  itemTpl = item => {
    let tpl = '';

    for (var [key, value] of Object.entries(item)) {
      if (typeof value === 'object') {
        tpl += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        tpl += `<li>${ key }: ${ value }</li>`;
      }
    }

    return tpl;
  }

  render () {
    const { info } = this.state;

    return <div>
      <ul dangerouslySetInnerHTML={{
        __html: info.map(item => this.itemTpl(item))
      }}></ul>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
class App extends React.Component {
  state = {
    info: []
  };

  componentDidMount () {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      this.setState({
        info: res
      });
    })();
  }

  itemTpl = item => {
    let tpl = '';

    for (var [key, value] of Object.entries(item)) {
      if (typeof value === 'object') {
        tpl += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        tpl += `<li>${ key }: ${ value }</li>`;
      }
    }

    return tpl;
  }

  render () {
    const { info } = this.state;

    return <div>
      <ul dangerouslySetInnerHTML={{
        __html: info.map(item => this.itemTpl(item))
      }}></ul>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

案例代码 2 ​

jsx
function withInfo (Com) {
  return class extends React.Component {
    state = {
      info: []
    };
  
    componentDidMount () {
      ;(async () => {
        const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());
  
        this.setState({
          info: res
        });
      })();
    }

    render () {
      return <Com {...this.state}></Com>;
    }
  }
}

export { withInfo };
function withInfo (Com) {
  return class extends React.Component {
    state = {
      info: []
    };
  
    componentDidMount () {
      ;(async () => {
        const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());
  
        this.setState({
          info: res
        });
      })();
    }

    render () {
      return <Com {...this.state}></Com>;
    }
  }
}

export { withInfo };
jsx
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    let tpl = '';

    for (var [key, value] of Object.entries(item)) {
      if (typeof value === 'object') {
        tpl += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        tpl += `<li>${ key }: ${ value }</li>`;
      }
    }

    return tpl;
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    let tpl = '';

    for (var [key, value] of Object.entries(item)) {
      if (typeof value === 'object') {
        tpl += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        tpl += `<li>${ key }: ${ value }</li>`;
      }
    }

    return tpl;
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));

案例代码 3 ​

js
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    return Object.entries(item).reduce((prev, [key, value]) => {
      if (typeof value === 'object') {
        prev += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        prev += `<li>${ key }: ${ value }</li>`;
      }
      return prev;
    }, '');
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    return Object.entries(item).reduce((prev, [key, value]) => {
      if (typeof value === 'object') {
        prev += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`;
      } else {
        prev += `<li>${ key }: ${ value }</li>`;
      }
      return prev;
    }, '');
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));

案例代码 4 ​

js
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));
import { withInfo } from '../service/index';

class List extends React.PureComponent {
  render () {
    const { info, render } = this.props;

    return (
      <div>
        <ul dangerouslySetInnerHTML={{
          __html: info.map(item => render(item))
        }}></ul>
      </div>
    )
  }
}

class App extends React.Component {
  itemTpl = item => {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ this.itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  render () {
    return <div>
      <List { ...this.props } render={ this.itemTpl } />
    </div>;
  }
}

const WithInfoApp = withInfo(App);

ReactDOM.render(<WithInfoApp />, document.getElementById('app'));

案例代码 5 ​

js
const { useState, useEffect } = React;

const useList = (info, itemTpl) => {
  return (
    <ul dangerouslySetInnerHTML={{
      __html: info.map(item => itemTpl(item))
    }}></ul>
  )
}

const App = params => {
  const [info, setInfo] = useState([]);

  useEffect(() => {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      setInfo(res);
    })();
  }, []);

  function itemTpl (item) {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  return useList(info, itemTpl);
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, useEffect } = React;

const useList = (info, itemTpl) => {
  return (
    <ul dangerouslySetInnerHTML={{
      __html: info.map(item => itemTpl(item))
    }}></ul>
  )
}

const App = params => {
  const [info, setInfo] = useState([]);

  useEffect(() => {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      setInfo(res);
    })();
  }, []);

  function itemTpl (item) {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  return useList(info, itemTpl);
}

ReactDOM.render(<App />, document.getElementById('app'));

案例代码 6 ​

js
const { useState, useEffect } = React;

function useInfo () {
  const [info, setInfo] = useState([]);

  useEffect(() => {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      setInfo(res);
    })();
  }, []);

  return info;
}

export { useInfo };
const { useState, useEffect } = React;

function useInfo () {
  const [info, setInfo] = useState([]);

  useEffect(() => {
    ;(async () => {
      const res = await fetch('http://jsonplaceholder.typicode.com/users').then(res => res.json());

      setInfo(res);
    })();
  }, []);

  return info;
}

export { useInfo };
jsx
import { useInfo } from '../service/index';

const useList = (info, itemTpl) => {
  return (
    <ul dangerouslySetInnerHTML={{
      __html: info.map(item => itemTpl(item))
    }}></ul>
  )
}

const App = () => {
  const info = useInfo();

  function itemTpl (item) {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  return useList(info, itemTpl);
}

ReactDOM.render(<App />, document.getElementById('app'));
import { useInfo } from '../service/index';

const useList = (info, itemTpl) => {
  return (
    <ul dangerouslySetInnerHTML={{
      __html: info.map(item => itemTpl(item))
    }}></ul>
  )
}

const App = () => {
  const info = useInfo();

  function itemTpl (item) {
    return Object.entries(item).reduce((prev, [key, value]) => {
      return typeof value === 'object' 
        ? prev += `<ul><li>${ key }: ${ itemTpl(value) }</li></ul>`
        : prev += `<li>${ key }: ${ value }</li>`;
    }, '');
  }

  return useList(info, itemTpl);
}

ReactDOM.render(<App />, document.getElementById('app'));

总结 ​

自定义 hook 是一种自然遵循 Hook 设计的约定,并不是 React 的特性。

自定义 hook 必须以 "use" 开头,这是一个约定。

如果不遵循此约定,React 无法自动检测 Hook 是否违反了 Hook 的规则。

多个组件使用相同的 hook 不会共享 state。

每次调用 Hook,都会获取独立的 state。

Reducer Hook ​

js
const { useState } = React;

function reducer (state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function useReducer (reducer, initialVal) {
  const [val, setVal] = useState(initialVal);

  const dispatch = (action) => {
    const newVal = reducer(val, action);
    setVal(newVal);
  }

  return [ val, dispatch ];
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      Count: { state.count }
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState } = React;

function reducer (state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function useReducer (reducer, initialVal) {
  const [val, setVal] = useState(initialVal);

  const dispatch = (action) => {
    const newVal = reducer(val, action);
    setVal(newVal);
  }

  return [ val, dispatch ];
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      Count: { state.count }
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
jsx
const { useState, useReducer } = React;

function reducer (state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      Count: { state.count }
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, useReducer } = React;

function reducer (state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      Count: { state.count }
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

useReducer 有两种初始化 state 的方式。

jsx
// 指定初始 state
const [state, dispatch] = useReducer(reducer, { count: initialCount });

// 惰性化 state,不常用
function init (initialCount) {
  return { count: initialCount };
}
const [state, dispatch] = useReducer(reducer, initialCount, init);
// 指定初始 state
const [state, dispatch] = useReducer(reducer, { count: initialCount });

// 惰性化 state,不常用
function init (initialCount) {
  return { count: initialCount };
}
const [state, dispatch] = useReducer(reducer, initialCount, init);

跳过 dispatch。

如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。

React 使用 Object.is 比较算法来比较 state。

Context Hook ​

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前值由上层组件距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

jsx
const { Component, createContext, useContext } = React;

const AppContext = createContext();

class Foo extends Component {
  render () {
    return (
      <AppContext.Consumer>
        {
          value => <div>Foo: { value }</div>
        }
      </AppContext.Consumer>
    );
  }
}

class Bar extends Component {
  static contextType = AppContext;

  render () {
    const value = this.context;

    return <div>Bar: { value }</div>;
  }
}

const Baz = () => {
  const value = useContext(AppContext);

  return <div>Baz: { value }</div>;
}

const Middle = () => {
  return (
    <div>
      <Foo></Foo>      
      <Bar></Bar>
      <Baz></Baz>
    </div>
  )
}

const App = () => {
  return (
    <AppContext.Provider value={'月落'}>
      <Middle></Middle>
    </AppContext.Provider>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
const { Component, createContext, useContext } = React;

const AppContext = createContext();

class Foo extends Component {
  render () {
    return (
      <AppContext.Consumer>
        {
          value => <div>Foo: { value }</div>
        }
      </AppContext.Consumer>
    );
  }
}

class Bar extends Component {
  static contextType = AppContext;

  render () {
    const value = this.context;

    return <div>Bar: { value }</div>;
  }
}

const Baz = () => {
  const value = useContext(AppContext);

  return <div>Baz: { value }</div>;
}

const Middle = () => {
  return (
    <div>
      <Foo></Foo>      
      <Bar></Bar>
      <Baz></Baz>
    </div>
  )
}

const App = () => {
  return (
    <AppContext.Provider value={'月落'}>
      <Middle></Middle>
    </AppContext.Provider>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));

useMemo、useCallback ​

把 ”创建“ 函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免每次渲染时都进行高开销的计算。传入 useMemo 的函数会在渲染期间执行。

不要在 useMemo 这个函数内部执行与渲染无关的操作,比如副作用这类的操作(useEffec)等。

jsx
const { useState } = React;

const Foo = props => {
  return <ul>{ props.render() }</ul>;
}

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = () => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState } = React;

const Foo = props => {
  return <ul>{ props.render() }</ul>;
}

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = () => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

=>

jsx
const { useState, memo } = React;

// memo 是函数组件优化的一种方式,不希望子组件运行,不是 useMemo
const Foo = memo(props => {
  return <div>
    { props.count }
    <ul>{ props.render() }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = () => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo count={ count } render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, memo } = React;

// memo 是函数组件优化的一种方式,不希望子组件运行,不是 useMemo
const Foo = memo(props => {
  return <div>
    { props.count }
    <ul>{ props.render() }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = () => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo count={ count } render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

=>

jsx
const { useState, memo, useCallback } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render() }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);
 
  // useCallback 可以确定一个函数,将函数固定
  const render = useCallback(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, memo, useCallback } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render() }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);
 
  // useCallback 可以确定一个函数,将函数固定
  const render = useCallback(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

=>

jsx
const { useState, memo, useCallback, useMemo } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);
 
  // useMemo 用来修饰值,固定值
  const render = useMemo(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, memo, useCallback, useMemo } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);
 
  // useMemo 用来修饰值,固定值
  const render = useMemo(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setCount(count + 1) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
jsx
const { useState, memo, useCallback, useMemo } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = useMemo(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setRange({
        ...range,
        max: range.max + 1
      }) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));
const { useState, memo, useCallback, useMemo } = React;

const Foo = memo(props => {
  console.log('foo trigger')

  return <div>
    <ul>{ props.render }</ul>
  </div>;
});

function App () {
  const [range, setRange] = useState({ min: 0, max: 10000 });
  const [count, setCount] = useState(0);

  const render = useMemo(() => {
    const list = [];

    console.log('tigger');

    for (let i = 0; i < range.max; i++) {
      list.push(<li key={ i }>{ i }</li>);
    }

    return list;
  }, [range]);

  return (
    <div>
      <h1>{ count }</h1>
      <button onClick={() => { setRange({
        ...range,
        max: range.max + 1
      }) }}>add</button>
      <Foo render={ render }></Foo>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

memo 用于组件优化、函数组件使用 memo,类组件使用 PureComponent。

useMemo 用来固定值,类组件传递固定值就可以。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。初次渲染。

useCallback 用来固定函数,类组件使用箭头函数的形式传递固定函数引用。

useCallback(fn. dps),相当于 useMemo(() => fn, deps)。

useRef ​

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。 返回的 ref 对象在组件的整个生命周期内保持不变。

jsx
const { createRef } = React;

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <input type="text" ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef } = React;

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <input type="text" ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
js
const { createRef } = React;

class Foo extends React.Component {
  inputRef = createRef();

  focus = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <input type="text" ref={ this.inputRef } />;
  }
}

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <Foo ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef } = React;

class Foo extends React.Component {
  inputRef = createRef();

  focus = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <input type="text" ref={ this.inputRef } />;
  }
}

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <Foo ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

类组件中,ref 可以是原生 DOM,也可以是子组件。

jsx
const { createRef, forwardRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <Foo ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef, forwardRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

class App extends React.Component {
  inputRef = createRef();

  onClick = () => {
    this.inputRef.current.focus();
  }

  render () {
    return <div>
      <Foo ref={ this.inputRef } />
      <button onClick={ this.onClick }>button</button>
    </div>;
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

函数组件中没有 ref,可以通过函数转发。

jsx
const { createRef, forwardRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = createRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef, forwardRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = createRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
jsx
const { createRef, forwardRef, useRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef, forwardRef, useRef } = React;

const Foo = forwardRef((params, inputRef) => {
  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));

钩子在每次执行时,都返回同一个引用。函数组件更新时会执行整个函数进行更新。

父组件通过 fowardRef 的方式获取到子组件的原生 DOM。可以删除原生 DOM(不太友好,权限太大)。

##useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露出父组件的实例值。可以有效控制父组件范围。

jsx
const { createRef, forwardRef, useRef, useImperativeHandle } = React;

const Foo = forwardRef((params, inputRef1) => {
  const inputRef = useRef();

  useImperativeHandle(inputRef1, () => ({
    focus () {
      inputRef.current.focus();
    }
  }));

  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef, forwardRef, useRef, useImperativeHandle } = React;

const Foo = forwardRef((params, inputRef1) => {
  const inputRef = useRef();

  useImperativeHandle(inputRef1, () => ({
    focus () {
      inputRef.current.focus();
    }
  }));

  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));

=>

jsx
const { createRef, forwardRef, useRef } = React;

// 自定义实现 ref
const useImperativeHandle = (ref, cb) => {
  ref.current = cb();
}

const Foo = forwardRef((params, inputRef1) => {
  const inputRef = useRef();

  useImperativeHandle(inputRef1, () => ({
    focus () {
      inputRef.current.focus();
    }
  }));

  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
const { createRef, forwardRef, useRef } = React;

// 自定义实现 ref
const useImperativeHandle = (ref, cb) => {
  ref.current = cb();
}

const Foo = forwardRef((params, inputRef1) => {
  const inputRef = useRef();

  useImperativeHandle(inputRef1, () => ({
    focus () {
      inputRef.current.focus();
    }
  }));

  return <input type="text" ref={ inputRef } />;
});

const App = () => {
  const inputRef = useRef();

  const onClick = () => {
    inputRef.current.focus();
  }

  return <div>
    <Foo ref={ inputRef } />
    <button onClick={ onClick }>button</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
js
const { useRef, createRef, useState } = React;

window.arr = [];

const App = params => {
  const [count, setCount] = useState(0);

  const useRef1 = useRef();
  const createRef1 = createRef();

  window.arr.push(useRef1, createRef1);

  return (
    <div>
      { count }
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));

// 每次刷新时,useRef 是同一引用,createRef 不是同一引用。
// arr[0] === arr[2];
// arr[1] !== arr[3];

//
const { useRef, createRef, useState } = React;

window.arr = [];

const App = params => {
  const [count, setCount] = useState(0);

  const useRef1 = useRef();
  const createRef1 = createRef();

  window.arr.push(useRef1, createRef1);

  return (
    <div>
      { count }
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));

// 每次刷新时,useRef 是同一引用,createRef 不是同一引用。
// arr[0] === arr[2];
// arr[1] !== arr[3];

//
js
class App extends React.Component {
  inputRef;

  onClick = () => {
    console.log(this.inputRef);
  }

  render () {
    return (
      <div>
        <input type="text" ref={ ref => this.inputRef = ref } />
        <button onClick={ this.onClick }>add</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
class App extends React.Component {
  inputRef;

  onClick = () => {
    console.log(this.inputRef);
  }

  render () {
    return (
      <div>
        <input type="text" ref={ ref => this.inputRef = ref } />
        <button onClick={ this.onClick }>add</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
js

const App = params => {
  let inputRef;

  const onClick = () => {
    console.log(inputRef);
  }

  return (
    <div>
      <input type="text" ref={ ref => inputRef = ref } />
      <button onClick={ onClick }>add</button>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

const App = params => {
  let inputRef;

  const onClick = () => {
    console.log(inputRef);
  }

  return (
    <div>
      <input type="text" ref={ ref => inputRef = ref } />
      <button onClick={ onClick }>add</button>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

useLayoutEffect ​

其用法和 useEffect 一致,不同的是它会在所有的 DOM 变更之后同步调用 effect。可以使用它读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新将被同步刷新。

如果将 class 组件迁移至 Hook 组件,useLayoutEffect 与 componentDidMount、componentDidUpdate 的调用阶段是一样的。不过推荐先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。useEffect 在 DOM 渲染完成之后执行。

jsx
// useEffect

const { useState, useEffect } = React;

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      console.log(count);
    }, 3 * 1000); 
  }, [count]);

  return (
   <div>
     <p>{ count }</p>
     <button
      onClick={() => setCount(count + 1)}
     >
       add
     </button>
   </div> 
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
// useEffect

const { useState, useEffect } = React;

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      console.log(count);
    }, 3 * 1000); 
  }, [count]);

  return (
   <div>
     <p>{ count }</p>
     <button
      onClick={() => setCount(count + 1)}
     >
       add
     </button>
   </div> 
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
jsx
// useEffect 存在动画效果

const { useState, useEffect, useRef } = React;

const App = () => {
  const ref = useRef();
  const [, setCount] = useState({});

  useEffect(() => {
    setTimeout(() => {
      ref.current.style.transform = 'translate(500px)';
      ref.current.style.transition = 'all .5s';
    }, 3 * 1000);

    return () => {
      ref.current.style.transform = 'translate(0px)';
      ref.current.style.transition = 'all .5s';
    }
  });

  let styleObj = {
    width: '100px',
    height: '100px',
    background: 'orange'
  };

  return <div>
    <div style={ styleObj } ref={ ref }></div>
      <button onClick={ () => setCount({}) }>Click</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));

// useLayoutEffect 无动画效果
const { useState, useEffect, useRef, useLayoutEffect } = React;

const App = () => {
  const ref = useRef();
  const [, setCount] = useState({});

  useLayoutEffect(() => {
    ref.current.style.transform = 'translate(500px)';
    ref.current.style.transition = 'all .5s';

    return () => {
      ref.current.style.transform = 'translate(0px)';
      ref.current.style.transition = 'all .5s';
    }
  });

  let styleObj = {
    width: '100px',
    height: '100px',
    background: 'orange'
  };

  return <div>
    <div style={ styleObj } ref={ ref }></div>
      <button onClick={ () => setCount({}) }>Click</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));
// useEffect 存在动画效果

const { useState, useEffect, useRef } = React;

const App = () => {
  const ref = useRef();
  const [, setCount] = useState({});

  useEffect(() => {
    setTimeout(() => {
      ref.current.style.transform = 'translate(500px)';
      ref.current.style.transition = 'all .5s';
    }, 3 * 1000);

    return () => {
      ref.current.style.transform = 'translate(0px)';
      ref.current.style.transition = 'all .5s';
    }
  });

  let styleObj = {
    width: '100px',
    height: '100px',
    background: 'orange'
  };

  return <div>
    <div style={ styleObj } ref={ ref }></div>
      <button onClick={ () => setCount({}) }>Click</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));

// useLayoutEffect 无动画效果
const { useState, useEffect, useRef, useLayoutEffect } = React;

const App = () => {
  const ref = useRef();
  const [, setCount] = useState({});

  useLayoutEffect(() => {
    ref.current.style.transform = 'translate(500px)';
    ref.current.style.transition = 'all .5s';

    return () => {
      ref.current.style.transform = 'translate(0px)';
      ref.current.style.transition = 'all .5s';
    }
  });

  let styleObj = {
    width: '100px',
    height: '100px',
    background: 'orange'
  };

  return <div>
    <div style={ styleObj } ref={ ref }></div>
      <button onClick={ () => setCount({}) }>Click</button>
  </div>;
}

ReactDOM.render(<App />, document.getElementById('app'));

如果使用服务端渲染,useLayoutEffect 和 useEffect 都无法使用。

useDebugValue ​

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

jsx
useDebugValue('这是重要的信息,不允许改动');
useDebugValue('这是重要的信息,不允许改动');

不推荐向每个自定义 hook 添加 debug 值,当它作为共享库的一部分时才最有价值。

Hook 原理 ​

React 保持对当前渲染中的组件的追踪,Hook 只会在 React 组件中被调用。

每个组件内部都有一个记忆单元格。它们用来存储一些数据的 JavaScript 对象。当你用 useState 调用一个 Hook 时,它会读取当前的单元格(首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。