1、如何指定context的类型
首先用interface定义了context的类型:
然后new一个context
这里为什么要用或(|)。因为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的使用
在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;
default:
return 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改变会触发的生命周期
componentWillReveicePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate
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的使用
父组件如何使用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>
</>
);
}