React学习笔记三

211 阅读5分钟

一  Hooks

1.1 React hooks

React hooks是React 16.8中的新增功能,它们使你无需编写类即可使用状态和其他react功能;

下面实现了一个hooks简单的demo

import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/*
  useState 状态
    [state, setState] = useState(initState)
      state 当前对应的状态
      setState修改state的方法
 */
function App() {
  const [name, setName] = useState('Stoney');
  return (
    <div className="App">
      <Child
        name={name}
        setName={setName}
      />
      <button>卸载</button>
    </div>
  );
}

export default App;

child.js

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

/**
 * useEffect 副作用
 * useEffect(cb)
 * useEffect(cb[,[依赖1, 依赖2]]);
 * useEffect相当于:componentDidMount componentDidUpdate componentWillUnmount合体
 */

function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18);
  useEffect(() => {
    console.log("组件更新了");
  });
  useEffect(() => {
    console.log("age变化导致组件更新了");
  }, [age]);
  useEffect(() => {
    console.log("name变化导致组件更新了");
  }, [name]);
  return (
    <div>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

React hooks优势:

  • 简化组件逻辑
  • 复用状态逻辑
  • 无需使用类组件编写

1.2 常用hook

1.2.1 useState

const [name, setName] = useState(initialState);

1.2.2 useEffect

类组件componentDidMount componentDidUpdate componentWillUnmount需要清除的副作用

下面demo通过类组件和函数式组件对比来演示useEffect功能

App.js代码

import React from 'react';
import Effect from './hook/effect';

function App() {
  return (
    <div>
      <Effect />
    </div>
  );
}

export default App;

effect.js代码

import React, { Component } from 'react';

class Txt extends Component{
  componentWillUnmount() {
    console.log('组件即将卸载');
  }
  render() {
    let { text, setEdit } = this.props;
    return (
      <div>{text} <a onClick={() => {
        setEdit({
          edit: true
        })
      }}>编辑</a></div>
    )
  }
}

class Effect extends Component {
  state = {
    text: '这是今天的课程',
    edit: false
  }
  setEditState = (edit) => {
    this.setState({
      edit
    })
  }
  componentDidMount() {
    console.log("组件挂载完毕");
  }
  componentDidUpdate() {
    console.log("组件更新完毕");
  }
  render() {
    let { text, edit } = this.state;
    return (<div>
      {
        edit ? 
          <input
            type = 'text'
            value = {text}
            onChange = {
              (e) => {
                this.setState({
                  text: e.target.value
                })
              }
            }
            onBlur = {
              () => {
                this.setEditState(false);
              }
            }
          />
          :
          <Txt text={text} setEdit={this.setEditState} />
      }
    </div>);
  }
}

export default Effect;

useEffect用法demo

App.js代码

import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/* 
  useState 状态
    [state, setState] = useState(initState)
      state 当前对应的状态
      setState修改state的方法
 */
function App() {
  const [name, setName] = useState('Stoney');
  const [show, setShow] = useState(true);
  return (
    <div className="App">
      {show ? <Child
        name={name}
        setName={setName}
      /> : ""}
      <button onClick={() => {
        setShow(!show)
      }}>{show ? '卸载' : '加载'}</button>
    </div>
  );
}

export default App;

clild.js

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

/**
 * useEffect 副作用
 * useEffect(cb)
 * useEffect(cb[,[依赖1, 依赖2]]);
 * useEffect相当于:componentDidMount componentDidUpdate componentWillUnmount合体
 * 只希望在组件挂载后执行某些事情(componentDidMount)
 */

function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18); 
  useEffect(() => {
    console.log("组件挂载完成之后");
    return () => {
      console.log("组件即将卸载时执行");
    }
  }, []);
  useEffect(() => {
    console.log("组件挂载完成之后及更新完成之后");
  });
  return (
    <div>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

加载卸载也可以使用自定义Hooks,代码如新:

import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/* 
  useState 状态
    [state, setState] = useState(initState)
      state 当前对应的状态
      setState修改state的方法
 */

function useToggle(init) {
  const [off, setOff] = useState(init);
  return [off, () => {
    setOff(!off);
  }];
}

function App() {
  const [name, setName] = useState('Stoney');
  // const [show, setShow] = useState(true);
  const [show, changeShow] = useToggle(true)
  return (
    <div className="App">
      {show ? <Child
        name={name}
        setName={setName}
      /> : ""}
      <button onClick={() => {
        changeShow(!show)
      }}>{show ? '卸载' : '加载'}</button>
    </div>
  );
}

export default App;

1.2.3 useContext

下面demo实现了跨级组件通信的例子,通过props实现:

import React, { Component, createContext } from 'react';

class Child extends Component {
  render() {
    return <strong>这是祖先传下来的宝贝:{this.props.info}</strong>
  }
}

class Parent extends Component {
  render() {
    return (
      <p>
        <Child  info={this.props.info} />
      </p>
    )
  }
}

class Context extends Component {
  render() {
    return <div>
      <Parent
        info={"今天天气不错"}
      />
    </div>
  }
}

export default Context;

context

主要API有createContext, Provider, Consumer, contextType;

下面demo实现了Provider和Consumer通信的例子

import React, { Component, createContext } from 'react';
let {Provider, Consumer} = createContext();

class Child extends Component {
  render() {
    return (
      <Consumer>
        {(context) => {
          return <strong>这是祖先传下来的宝贝:{context.info}</strong>
        }}
      </Consumer>
    )
  }
}

class Parent extends Component {
  render() {
    return (
      <p>
        <Child />
      </p>
    )
  }
}

class Context extends Component {
  render() {
    return <div>
      <Provider value={{info: '今天天气不错'}}>
        <Parent/>
      </Provider>
    </div>
  }
}

export default Context;

contextType用法:

import React, { Component, createContext } from 'react';
import { ConsoleWriter } from 'istanbul-lib-report';

let myContext = createContext();

class Child extends Component {
  static contextType  = myContext;
  render() {
    console.log(this.context)
    return (
      <strong>这是祖先传下来的宝贝:{this.context.info}</strong>
    )
  }
}

function Child2() {
  return (
    <myContext.Consumer>
      {
        context => {
          console.log(context);
          return <div><strong>这是祖先传下来的宝贝:{context.info}</strong></div>
        }
      }
    </myContext.Consumer>
  )
}

class Parent extends Component {
  render() {
    return (
      <div>
        <Child />
        <Child2 />
      </div>
    )
  }
}

class Context extends Component {
  render() {
    return <div>
      <myContext.Provider value={{info: '今天天气不错'}}>
        <Parent/>
      </myContext.Provider>
    </div>
  }
}

export default Context;

useContext用法:

import React, { Component, createContext, useContext } from 'react';

let myContext = createContext();

class Child extends Component {
  static contextType  = myContext;
  render() {
    console.log(this.context)
    return (
      <strong>这是祖先传下来的宝贝:{this.context.info}</strong>
    )
  }
}

function Child2() {
  let {info} = useContext(myContext);
  return (
    <div><strong>这是祖先传下来的宝贝:{info}</strong></div>
  )
}

class Parent extends Component {
  render() {
    return (
      <div>
        <Child />
        <Child2 />
      </div>
    )
  }
}

class Context extends Component {
  render() {
    return <div>
      <myContext.Provider value={{info: '今天天气不错'}}>
        <Parent/>
      </myContext.Provider>
    </div>
  }
}

export default Context;

1.2.4 useReducer

下面demo是useReducer的用法:

import React, { useReducer, createContext, useContext } from 'react';

let myContext = createContext();

function reduce(state = 0, action) {
  switch(action.type) {
    case 'add':
      state += 1;
      break;
    case 'minus':
      state -= 1;
      break;
  }
  return state;
}

function Child() {
  let {state, dispatch} = useContext(myContext)
  return (
    <div>
      <button
        onClick={() => {
          dispatch({
            type: 'minus'
          });
        }}
      >-</button>
      <span> {state} </span>
      <button
        onClick={() => {
          dispatch({
            type: 'add'
          });
        }}
      >+</button>
    </div>
  )
}

function Reducer() {
  let [state, dispatch] = useReducer(reduce, 0);
  return (
    <myContext.Provider value={{
      state,
      dispatch
    }}>
      <Child />
    </myContext.Provider>
  )
}

export default Reducer;

1.2.5 useCallback

useCallback和useMemo都会在组件第一次渲染的时候执行,之后每次会在其依赖的变量发生改变时再次执行;并且这两个hook都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数;

下面demo是useCallback的用法;

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

function Callback() {
  const [name, setName] = useState("leo");
  const [age, setAge] = useState(10);
  let age2 = useCallback(() => age < 18 ? "未成年" : "成年人", [age < 18]);
  return (<div>
    姓名:{name}, <br />
    年龄:{age}, <br />
    年龄阶段:{age2()}, <br />
    <button onClick={() => {
      setAge(age + 2);
    }}>长大</button>
  </div>)
}

export default Callback;

1.2.6 useRef 用法

获取真实dom

import React, {useState, useEffect, useRef} from 'react';


function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18);
  const div = useRef(null);
  useEffect(() => {
    console.log(div.current);
  });
  return (
    <div ref={div}>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

打印如下图所示:

记录组件更新之前的值

import React, { useState } from 'react';
import Child from './Child';

function App() {
  const [name, setName] = useState('Stoney');
  const [show, setShow] = useState(true);
  return (
    <div className="App">
      {show ? <Child
        name={name}
        setName={setName}
      /> : ""}
      <button onClick={() => {
        setShow(!show)
      }}>{show ? '卸载' : '加载'}</button>
    </div>
  );
}

export default App;

Child.js

import React, {useState, useEffect, useRef} from 'react';


function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18);
  const div = useRef(null);
  const preVal = useRef({
    name,
    age
  });
  /**
   * useRef(defaultVal);
   * 1,获取真实的dom
   * 2,记录组件更新之前的值
   */
  useEffect(() => {
    console.log(div.current);
    console.log(preVal.current, name, age);
    preVal.current = {
      name, age
    }
  });
  return (
    <div ref={div}>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

可以添加一个开关,判断组件是挂载还是更新,代码如下所示:

import React, {useState, useEffect, useRef} from 'react';


function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18); 
  const div = useRef(null);
  const preVal = useRef({
    name,
    age
  });
  /**
   * useRef(defaultVal);
   * 1,获取真实的dom
   * 2,记录组件更新之前的值
   */

  useEffect(() => {
    if(!preVal.current) {
      console.log("更新")
    } else {
      console.log("挂载");
      preVal.current = false;
    }
  });
  return (
    <div ref={div}>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

下面demo通过ref来给输入框组件自动获取焦点的功能

import React, { useState, useEffect, useRef } from 'react';

function Txt(props){
  let { text, setEdit } = props;
  return (
    <div>{text} <a onClick={() => {
      setEdit(true)
    }}>编辑</a></div>
  )
}
function Edit(props) {
  const {text, setText, setEdit} = props;
  let t = useRef(null);
  function toScroll() {
    let y = window.scrollY;
    t.current.style.transform = `translateY(${y}px)`;
  }
  useEffect(() => {
    window.addEventListener("scroll", toScroll);
    t.current.select();
    return () => {
      console.log("组件即将卸载");
      window.removeEventListener("scroll", toScroll);
    }
  }, [])
  return (<input
    type = 'text'
    value = {text}
    id="txt"
    ref = {t}
    onChange = {
      (e) => {
        setText(e.target.value)
      }
    }
    onBlur = {
      () => {
        setEdit(false);
      }
    }
  />)
}
function Effect() {
  const [text, setText] = useState('这是今天的课程');
  const [edit, setEdit] = useState(false);
  // 只监听edit发生改变
  useEffect(() => {
    console.log("组件更新了");
  }, [edit]);
  return (<div>
    {
      edit ? 
        <Edit
          text={text}
          setText={setText}
          setEdit={setEdit}
        />
        :
        <Txt text={text} setEdit={setEdit} />
    }
    {[...(".".repeat(100))].map((item, index) => {
      return <div key={index}>页面内容填充</div>
    })}
  </div>);
}

export default Effect;

下面demo通过ref拿到上一次值的功能

import React, { useState, useEffect, useRef } from 'react';

function Ref() {
  const [nub, setNub] = useState(0);
  const prev = useRef(nub);
  useEffect(() => {
    prev.current = nub;
  })
  return (<div>
    <p>当前值: {nub}</p>
    <p>上次值: {prev.current}</p>
    <button onClick={() => {
      setNub(nub + 1);
    }}>递增</button>
  </div>);
}

export default Ref;

1.2.7 useMemo

demo如下:

import React, { useState } from 'react';
import Child from './Child';

function App() {
  const [name, setName] = useState('Stoney');
  const [show, setShow] = useState(true);
  return (
    <div className="App">
      {show ? <Child
        name={name}
        setName={setName}
      /> : ""}
      <button onClick={() => {
        setShow(!show)
      }}>{show ? '卸载' : '加载'}</button>
    </div>
  );
}

export default App;

Child.js代码

import React, {useState, useEffect, useMemo} from 'react';


function Child(props) {
  const {name, setName} = props;
  const [age, setAge] = useState(18); 
  const val = useMemo(() => {
    console.log("组件即将挂载及更新")
    return `姓名: ${name}, 年龄: ${age}`;
  }, [name, age]);
  // console.log(val)
  useEffect(() => {
    console.log("组件挂载完成或更新完成")
  });
  console.log("组件挂载或更新");
  return (
    <div>
      <p>{val}</p>
      <p>name: {name} <br />
        <input
          type = "text"
          value={name}
          onChange = {({target}) => {
            setName(target.value);
          }}
        />
      </p>
      <p>age: {age} <br />
        <input
          type = "text"
          value={age}
          onChange = {({target}) => {
            setAge(target.value);
          }}
        />
      </p>
    </div>
  )
}

export default Child;

下面demo是memo的用法:

import React, { useState, useMemo, useEffect } from 'react';

function Memo() {
  const [name, setName] = useState("leo");
  const [age, setAge] = useState(10);
  let age2 = useMemo(() => {
    console.log(1);
    return age <= 18 ? '未成年' : '成年人';
  }, [age <= 18]);
  useEffect(() => {
    console.log("更新完成之后")
  }, [age]);
  console.log("开始更新了");
  return (<div>
    姓名:{name}, <br />
    年龄:{age}, <br />
    年龄阶段:{age2}, <br />
    <button onClick={() => {
      setAge(age + 2);
    }}>长大</button>
  </div>)
}

export default Memo;

1.3 自定义Hook

通过自定义Hook,可以将组件逻辑提取到可重用函数中;

下面demo展示了自定义hook的用法:

import React, {useState} from 'react';

function useCount(init) {
  let [count, setCount] = useState(init);
  function add() {
    count++;
    setCount(count);
  }
  function minus() {
    count--;
    setCount(count);
  }
  return [count, add, minus, setCount];
}

function Hook() {
  let [count, add, minus, setCount] = useCount(0)
  return (
    <div>
      <button
        onClick={() => {
          minus();
        }}
      >-</button>
      <span> {count} </span>
      <button
        onClick={() => {
          add();
        }}
      >+</button>
      <button onClick={() => {
        setCount(5);
      }}
      >自定义设置</button>
    </div>
  )
}

export default Hook;

1.3 hook使用规则

只在最顶层使用Hook,不要在循环,条件判断或者子函数中调用;

只在React函数中调用hook
React函数组件中
React Hook中