各种Hooks的使用(usecontext等等)

431 阅读4分钟

1、如何指定context的类型

首先用interface定义了context的类型:

image.png

然后new一个context

image.png

这里为什么要用或(|)。因为createContext的时候,初始值为空,也就是null。如果直接在范型里制定context的类型是ContextProps,那么null不符合该类型。还有一种方法,类型断言。把空对象断言为ContextProps,也不会报错。

2、如何指定函数组件Props的类型?PropsWithChildren如何使用?

现在有一个APP组件,它的父组件会传给它一个message变量,是一个字符串,还会给它传一些孩子(ReactNode)。常见的写法是:

type AppProps = {
  message: string
  children?: React.ReactNode
}

const App = ({ message, children }: AppProps) => (
  <div>
    {message}
    {children}
  </div>
)

或者下面这种写法:

type AppProps = {
  message: string
}

const App: React.FC<AppProps> = ({ message, children }) => (
  <div>
    {message}
    {children}
  </div>
)

PropsWithChildren的用法就是你不用手动指定children的类型,因为他一定是Reactnode。

type AppProps = React.PropsWithChildren<{ message: string }>
const App = ({ message, children }: AppProps) => (
  <div>
    {message}
    {children}
  </div>
)

3、Redux的使用

image.png

在index.ts中创建store:

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer,[]);
export default store;

reducer函数如下:

const reducer = (state: StateProps[] = [], action:ActionProps)=> {
    switch(action.type){
        case types.ADD:
            return [...state,action.newItem];

        case types.CHANGEFINISHED:
            const newItemList = state.map((item)=>{
                if(item.id==action.id)
                     console.log(item.isFinished);
                    return Object.assign({},
                        item,
                        {isFinished: !item.isFinished})
                }  
                return item;
            });
           return newItemList; 
           defaultreturn state;
    }
}

export type RootState = ReturnType<typeof reducer>;
export default reducer;

在TodiList根组件引入store:

import store from '../../store/index';
import { Provider } from 'react-redux';
const TodoList = ()=>{
  return (  
    <Provider store = {store}>
    <div className="todo-list">
      This is TodoList
      <TodoInput ></TodoInput>
      <TodoListContainer></TodoListContainer>
    </div>
    </Provider>
  )
}
export default TodoList;

如何在子组件中拿到dispatch和state:

import {useDispatch} from 'react-redux';
import { useSelector } from "react-redux";

    const dispatch = useDispatch();
    const state = useSelector((state: RootState)=>state);

4、useReducer的使用

在根组件拿到state和dispatch方法,并通过context传给子组件

export interface ContextProps{
  state: StateProps[],
  dispatch:React.Dispatch<ActionProps>,
}
export interface ActionProps{
  type: string;
  [key: string]: any;
}


export const MyContext = createContext<ContextProps>({} as ContextProps);
const MyProvider = ( props:React.PropsWithChildren<{}> )=>{
    const initState: StateProps[] = [];
  const [state, dispatch] = useReducer(reducer, initState);

    return(
       <MyContext.Provider value={{
           state,
           dispatch
            }}>
         {props.children}
  
        </MyContext.Provider>
    )
}
export default MyProvider;

子组件如何拿到dispatch方法和state?

const { state,dispatch } = useContext(MyContext);

5、componentDidUpdate什么时候执行

props改变会触发的生命周期

  1. componentWillReveiceProps
  2. shouldComponentUpdate
  3. componentWillUpdate
  4. render
  5. componentDidUpdate

6、memo的使用场景

const Child = (props) => {
    console.log('子组件?')
    return(
        <div>我是一个子组件</div>
    );
}
const Page = (props) => {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <Child />
        </>
    )
}

从上面代码可以看出,Page是父组件,点击按钮后,setConut改变了Count的值,因此Page会刷新。父组件刷新必然导致子组件刷新。因此Child会刷新。但是Count改变本没必要引起Child刷新。 因此把子组件用memo包裹起来:

const Child = (props) => {
    console.log('子组件?')
    return(
        <div>我是一个子组件</div>
    );
}
const ChildMemo = memo(Child);
const Page = (props) => {
    const [count, setCount] = useState(0);

    return (
        <>
            <button onClick={(e) => { setCount(count+1) }}>加1</button>
            <p>count:{count}</p>
            <ChildMemo />
        </>
    )
}

memo什么时候失效?当传给memo的prop发生改变,memo包裹的组件会重新渲染。看以下代码。只要props不改变,子组件永远不会重新渲染,props一旦改变,就会重新渲染。

const HYButton = memo(props)=>{
    return <button onClick = {props.increment}>+1</button>
}

7、useCallback的使用场景

 export default function Parent(){
     const [count,setCount] = useState(0);
     const [show,seShow] = useState(true);
     
     const increment1 = ()=>{
         setCount(count+1);
     }
     const increment2 = useCallback(()=>{
         setCount(count+1);
     },[count]);
     
     return(
     <div>
       <ButtonOne increment={increment1}/>
       <ButtonTwo increment={increment2}/>
       
       <button onCLick={e => setShow(!show)}>show切换</button>
     </div>
     )
 }

在上面代码中,点击button,触发setShow,Parent组件会重新渲染。导致increment1被重新定义。此时useCallback也会重新执行,但是返回给increment2的函数仍然是上次的函数(只要count没有被改变)。

最终结果就会,ButtonOne重新渲染,因为其props(increment)改变,而ButtonTwo没有被重新渲染,因为其参数increment2没有改变。

useCallback可能出现的问题:

export default function Index() {
    const [text, updateText] = useState('Initial value');

    const handleSubmit = useCallback(() => {
        console.log(`Text: ${text}`); // BUG:每次输出都是初始值
    }, []);

    return (
        <>
            <input value={text} onChange={(e) => updateText(e.target.value)} />
            <p onClick={handleSubmit}>useCallback(fn, deps)</p> 
        </>
    )
}

问题:每次在input框输入,调用onChange方法,onChange触发updateText方法,修改text的值,组件刷新。而匿名函数()=> { console.log(Text: ${text});没有被销毁,而该函数由于使用到第一次渲染时的text,导致第一次渲染时的text也被保留下来了(闭包)。

8、useMemo的使用场景

一句话总结:useMemo的第一个参数是一个回调函数,只要依赖不变,就直接拿上次的return值,而不需要重新执行回调

点击按钮,父组件重新渲染。注意,每次执行父组件,info都会重新创建一次,然后在函数执行结束销毁。因此每次都是一个新的info,HYInfo接受的prop改变了,自然memo就失效了。

const HYInfo = memo((props) => {
    return <h2>我是子组件</h2>
})
const default function Parent(){
    const [show,setShow] = useState(true);
    const info = { name:"why",age:18 };
    
    return(
    <div>
        <HYInfo info={info} />
        <button onClick={e => setShow(!show)}>切换</button>
    </div>
    )
}

想要把info保存起来,避免函数执行结束后被销毁,可以使用以下方法。info永远不会被销毁。

const info = useMemo(() => {
    return { name:"why",age:18 };
},[])

9、context的使用

blog.csdn.net/NinthMonee/…

父组件如何使用Context?

const MyContext = React.createContext(defaultValue);
<MyContext.Provider value={{something:'something'}}>
   <Toolbar />
<MyContext.Provider>

如此一来,在Toolbar内即可使用value这个对象:

<MyContext.Consumer>
{value =>/*自定义函数*/}
</MyContext.Consumer>

一旦value发生变化,provider会重新渲染,comsumer也会发生渲染

10、useRequest会导致组件重新渲染吗

会导致,run(也就是发送请求)的时候渲染一次,load为ture(也就是收到请求)的时候渲染一次

function generateName() {
  return new Promise(resolve => {
    setTimeout(() => {
      const name = Mock.mock('@name');
      Message.success(`请求成功,生成的 Name 是 ${name}`);
      resolve();
    }, 1000);
  });
}

function App() {
  const { loading, run } = useRequest(generateName);
  console.log("APP重新渲染"+loading);
  return (
    <>
      <div>
        <Button disabled={loading} type="primary" onClick={run}>
          {loading ? 'loading' : 'send request'}
        </Button>
      </div>
    </>
  );
}