http://img.ylzzxt.cn/image/20191220160826-7KXlxr.png

useState


使用方法

const [n, setN] = React.useState(0)
const [user, setUser] = React.useState({name:'F'})

注意事项

  1. 不可局部更新

     const [user,setUser] = useState({name:'Evan', age: 18})
     const onClick = ()=>{
     	// 不支持局部刷新
       setUser({
         name: 'Yan',
       })
     
     	// 需要先拷贝,并重新生成对象地址
     	setUser({
     		...user,
         name: 'Yan',
       })
     
     }
    
    • setState不会帮我们合并属性
    • useReducer也不会合并属性
  2. 地址要变

    • setState(obj)如果obj内存地址不变,那么React就认为数据没有变化
  3. useState 可接受函数

     const [state, setState] = useState(()=>{
     		return initialState
     })
    
    • 该函数返回初始state,且只执行一次
  4. setState 可接受函数

    • 基本写法:setN( 1=>1+1)}

    • 什么时候用这种方式?

      const [n, setN] = useState(0)
      const onClick = ()=>{
      // 你会发现 n 不能加 2
      setN(n+1)
      setN(n+1)

        // 需要以下写法
      setN(i=>i+1)
      setN(i=>i+1)
      

      }

    • 如果你能接受这种形式,应该优先使用这种形式

总结

  1. React只做它该做的事情,或者只提供工具,把值拷贝这类操作交给开发者
  2. 函数是一等公民,所以useState和setState都可接受函数为参数,且优先推荐

useReducer


使用方法

// 共分4步走
// 一、创建初始值initialState
const initial = {
  n: 0
};

// 二、创建所有操作reducer(state, action)
const reducer = (state, action) => {
  if (action.type === "add") {
    return { n: state.n + action.number };
  } else if (action.type === "multi") {
    return { n: state.n * 2 };
  } else {
    throw new Error("unknown type");
  }
};

function App() {
	// 三、传给useReducer,得到读和写API
  const [state, dispatch] = useReducer(reducer, initial);
  const { n } = state;
  const onClick = () => {
		// 四、调用写({type:操作类型'})
    dispatch({ type: "add", number: 1 });
  };
  const onClick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  );
}
  1. 用来践行Flux/Redux的思想
  2. 总得来说useReducer是useState的复杂版
  3. 一个用useReducer的表单例子

如何代替Redux

  1. 将数据集中在一个store对象
  2. 将所有操作集中在reducer
  3. 创建一个Context
  4. 创建对数据的读写API
  5. 将第四步的内容放到第三步的Context
  6. 用Context.Provider将Context提供给所有组件
  7. 各个组件用useContext获取读写API

useContext


上下文

  • 全局变量是全局的上下文
  • 上下文是局部的全局变量

使用方法

  • 使用C = createContext(initial)创建上下文
  • 使用<C.provider>圈定作用域
  • 在作用域内使用useContext(C)来使用上下文
    const C = createContext(null);
    
    function App() {
      console.log("App 执行了");
      const [n, setN] = useState(0);
      return (
        <C.Provider value={{ n, setN }}>
          <div className="App">
            <Baba />
          </div>
        </C.Provider>
      );
    }
    
    function Baba() {
      const { n, setN } = useContext(C);
      return (
        <div>
          我是爸爸 n: {n} <Child />
        </div>
      );
    }
    
    function Child() {
      const { n, setN } = useContext(C);
      const onClick = () => {
        setN(i => i + 1);
      };
      return (
        <div>
          我是儿子 我得到的 n: {n}
          <button onClick={onClick}>+1</button>
        </div>
      );
    }

注意事项

不是响应式的

  • 你在一个模块将C里面的值改变
  • 另一个模块不会感知到这个变化

useEffect


副作用

  • 对环境的改变即为副作用,如修改document.title
  • 但我们不一定非要把副作用放在useEffect里
  • 实际上叫做afterRender更好,每次render后运行

用途

  • 作为componentDidMount使用,[]作第二个参数
  • 作为componentDidUpdate使用,可指定依赖,如[a, b]
  • 作为componentWillUnmount使用,通过return
  • 以上三种用途可同时存在

特点

  • 如果同时存在多个useEffect,会按照出现次序执行

useLayoutEffect


布局副作用

  • useEffect在浏览器渲染完成后执行

      const BlinkyRender = () => {
        const [value, setValue] = useState(0);
      
        useEffect(() => {
          document.querySelector('#x').innerText = `value: 1000`
        }, [value]);
      
        return (
          <div id="x" onClick={() => setValue(0)}>value: {value}</div>
        );
      };
    
  • useLayoutEffect在浏览器渲染前执行

  • 通过时间点来侧面证明 (此处要画图说明)

      function App() {
        const [n, setN] = useState(0)
        const time = useRef(null)
        const onClick = ()=>{
          setN(i=>i+1) 
          time.current = performance.now()
        }
        useLayoutEffect(()=>{ // 改成 useEffect 试试
          if(time.current){
            console.log(performance.now() - time.current)
          }
        })
        return (
          <div className="App">
            <h1>n: {n}</h1>
            <button onClick={onClick}>Click</button>
          </div>
        );
      }
    

特点

  • useLayoutEffect总是比useEffect先执行

      function App() {
        const [n, setN] = useState(0)
        const onClick = ()=>{
          setN(i=>i+1)
        }
        useEffect(()=>{
          console.log("useEffect")
        })
        useLayoutEffect(()=>{ // 改成 useEffect 试试
          console.log("useLayoutEffect")
        })
        return (
          <div className="App">
            <h1>n: {n}</h1>
            <button onClick={onClick}>Click</button>
          </div>
        ); 
      }
    
  • useLayoutEffect里的任务最好影响了Layout

经验

  • 为了用户体验,优先使用useEffect (优先渲染)

useMemo


用法

  • 要理解React. useMemo需要先理解React.memo

  • React默认有多余的render

  • 讲代码中的Child用React.memo(Child)代替

  • 如果props不变,就没有必要再次执行一个函数组件

  • 最终效果:

      function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
          setN(n + 1);
        };
      
        return (
          <div className="App">
            <div>
              <button onClick={onClick}>update n {n}</button>
            </div>
            <Child data={m}/>
            {/* <Child2 data={m}/> */}
          </div>
        );
      }
      
      function Child(props) {
        console.log("child 执行了");
        console.log('假设这里有大量代码')
        return <div>child: {props.data}</div>;
      }
      
      const Child2 = React.memo(Child);
    

问题

  • 这玩意有一个bug

      function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
          setN(n + 1);
        };
        const onClickChild = () => {
          console.log(m);
        };
      
        return (
          <div className="App">
            <div>
              <button onClick={onClick}>update n {n}</button>
            </div>
            <Child2 data={m} onClick={onClickChild} />
            {/* Child2 居然又执行了 */}
          </div>
        );
      }
      
      function Child(props) {
        console.log("child 执行了");
        console.log("假设这里有大量代码");
        return <div onClick={props.onClick}>child: {props.data}</div>;
      }
      
      const Child2 = React.memo(Child);
    
  • 添加了监听函数之后,一秒破功

  • 因为App运行时会在次执行第12行,生成新的函数

  • 新旧函数虽然功能一样,但是地址不一样!

  • 怎么办?用useMemo:

      function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
          setN(n + 1);
        };
        const onClick2 = () => {
          setM(m + 1);
        };
        const onClickChild = useMemo(() => {
          const fn = div => {
            console.log("on click child, m: " + m);
            console.log(div);
          };
          return fn;
        }, [m]); // 这里呃 [m] 改成 [n] 就会打印出旧的 m
        return (
          <div className="App">
            <div>
              <button onClick={onClick}>update n {n}</button>
              <button onClick={onClick2}>update m {m}</button>
            </div>
            <Child2 data={m} onClick={onClickChild} />
          </div>
        );
      }
      
      function Child(props) {
        console.log("child 执行了");
        console.log("假设这里有大量代码");
        return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
      }
      
      const Child2 = React.memo(Child);
    

特点

  • 第一个参数是()=> value,见定义
  • 第二个参数是依赖[m,n]
  • 只有当依赖变化时,才会计算出新的value
  • 如果依赖不变,那么就重用之前的value
  • 这不就是Vue 2的computed吗?

注意

  • 如果你的value是个函数,那么你就要写成useMemo(()=> (x) => console.1og(x))
  • 这是一个返回函数的函数
  • 是不是很难用?于是就有了useCallback

useCallback


用法

  • useCallback(x => log(x), [m]) 等价于
  • useMemo(() => x => 1og(x), [m])

useRef


目的

  • 如果你需要一个值,
  • 在组件不断render时保持不变
  • 初始化: const count = useRef(0)
  • 读取: count.current
  • 为什么需要current?
  • 为了保证两次useRef是同一个值(只有引用能做到)
  • 此处需要画图解释

延伸

  • 看看Vue3的ref
  • 初始化: const count = ref(0)
  • 读取: count.value
  • 不同点:当count.value变化时,Vue 3会自动render

能做到变化时自动render吗?

  • 不能!
  • 为什么不能?因为这不符合React的理念
  • React的理念是UI = f(data)
  • 你如果想要这个功能,完全可以自己加
  • 监听ref,当ref.current变化时,调用setX即可

不想自己加?

  • 那你就用Vue3吧,Vue3帮你加好了

forwardRef


讲了useRef就不得不讲一下它了

  • 代码1: props 无法传递ref属性
  • 代码2: 实现ref的传递
  • 代码3: 两次ref传递得到button的引用

useRef

  • 可以用来引用DOM对象
  • 也可以用来引用普通对象

forwardRef

  • 由于props不包含ref,所以需要forwardRef
  • 为什么props不包含ref呢?因为大部分时候不需要

uselmperativeHandle


名字起得稀烂

分析

  • 用于自定义ref的属性

自定义Hook


封装数据操作

  • 简单例子
  • 贴心例子

分析

  • 你还可以在自定义Hook里使用Context
  • useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可

Stale Closure