react hooks
useEffect
react16.8之后推出的Hooks可以说在某些场景下优势十分明显,比如:useState、useEffect,在一个组件内比较完整的区分了逻辑部分以及ui部分,不用再把精力放到生命周期的维护上,可能大家在写代码的时候感觉不太到明显的优势,但是后期维护的时候,效果就会非常的明显:
useEffect(() => {
const keyboardShow = (e) => {
setKeyboardHeight(e.endCoordinates.height);
};
const keyboardHide = (e) => {
setKeyboardHeight(0);
};
Keyboard.addListener('keyboardWillShow', keyboardShow);
Keyboard.addListener('keyboardWillHide', keyboardHide);
return () => {
Keyboard.removeListener('keyboardWillShow', keyboardShow);
Keyboard.removeListener('keyboardWillHide', keyboardHide);
};
}, []);
一个useEffect就完整的解决了一个keyboard的添加监听以及移除监听,而且他俩都是联合起来使用的,其一,不太容易让你忘掉移除监听而导致内存泄漏,其二,后期维护的时候不至于你删除某一段代码而忘记删除另一段,一目了然。
useEffect用起来非常的灵活,可以根据具体的需求添加多个useEffect,这样逻辑上面是非常清楚的,后期维护想择出某段逻辑也非常容易,另外,useEffect第二个参数也很有用处,第二个参数是个数组,这个数组里面的值,决定了这个useEffect内的函数什么时候执行,例如:

右上角的save
按键的状态是不是disable,需要依赖于几个inpput的值是否为空,如果都不为空它的状态才是enable,其余情况都是disable,当然这里没有说校验输入的值是否正确之类,这些都是额外的一些很容易往上面加的模块,当然实现的方式也有很多种,但是我觉得用useEffect是合适的一种:
useEffect(() => {
if (
name.length == 0 ||
cardNum.length == 0 ||
date.length == 0 ||
cvv.length == 0
) {
setDisable(true);
} else {
setDisable(false);
}
}, [name, cardNum, date, cvv]);
逻辑看起来是不是很清楚……
useContext
好了,我们还是来说useContext、useReducer,useContext从字面意思来看,就是上下文的应用嘛,其实它就是这个作用,当你有一些状态或配置类的样式要层层传下去的时候用它很合适。
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
官方demo来看很直白,要把当前的这个样式传递下去,那就用context就可以,当然也可以是用户信息之类的一些要传递的东西。
例子中我们可以看出ThemedButton是App的孙子节点了,如果不用context我们用个全局变量之类也可以解决这个问题,他可以无限制的传递下去,但是前提是:你的父子传承关系不能变,是需要像树一样有一个共同的Context,这样才可以,如果你在A页面定义了Context,然后A页面push到B页面,想在B页面拿到A定义的Context里面包含的信息,是拿不到的,因为他俩不在一棵树上了,也就没有上下文了……,那么现在看,useContext是不是有点鸡肋……
useReducer
先不看Reducer字面含义,定义一个reducer:
const initialState = {count: 0};
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();
}
}
接受两个参数,一个是当前的state,一个是操作state的action。
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
当看到他的具体用法的时候,我们发现它完全可以替代useState来用,官方也有说明,他可以在某些状态十分复杂的组件内替换useState,似乎用处也不大……
useContext+useReducer
其实useReducer的dispatch触发机制,已经看起来就是redux了……,看起来非常亲切,前提是你用过redux或者dva……。
现在假设有一个需求,需要在A的子组件B中改变A的某些state,这时候useContext+useReducer就派上用场了,当然这里同样有n种实现方法,用redux的话直接不叫事,不用redux,通过callback也可以实现,这里我们只说useContext、useReducer:
export const AlertContext = React.createContext({});
const initialState = {
visible: false,
message: '',
title: '',
color: colors.success,
onDismiss: () => {},
action: () => {},
showSheet: false,
};
function reducer(state, action) {
switch (action.type) {
case 'changAlertState':
return { ...state, ...action.payload };
case 'showPaymentRemoveSheet':
return { ...state, ...action.payload };
default:
throw new Error();
}
}
A组件定义AlertContext以及Reducer,并把dispatch方法通过Context传递给下面所有子组件:
const [
{ visible, message, color, onDismiss, title, showSheet },
dispatch,
] = useReducer(reducer, initialState);
<AlertContext.Provider value={{ dispatch }}>
{/* {showSheet && renderSheet()} */}
<View style={styles.container}>
<SafeAreaView style={{ maxHeight: 64 }}>
<AppBar></AppBar>
</SafeAreaView>
<View>
<UserHeader needEdit></UserHeader>
</View>
<Alert
visible={visible}
message={message}
title={title}
color={color}
onDismiss={onDismiss}
/>
<HorizontalMenu></HorizontalMenu>
</View>
</AlertContext.Provider>
B组件拿到Context中的dispatch方法,然后触发reducer,对A的状态进行修改,
const { dispatch } = useContext(AlertContext);
dispatch({
type: 'changAlertState',
payload: {
visible: true,
message: 'New address added.',
color: colors.success,
title: 'Address Added',
},
});
这里就基本展示了useContext+useReducer所能干的事了,很显然,他就是某个局部内的redux作用,但是它能不能取代redux呢?我觉得是不能的,局限性很是很大,如最早我们所说,context只能上下文来传递值,同样dispatch也只能在上下文传递,只能是一颗树上,然后我们的APP显然要分好多棵树,很多个状态树,如果把这些所有要共享的状态都挂到APP根节点,我觉得不太合适,太庞大容易乱掉,不太好管理。
总结:让专业的人干专业事,逻辑部分还是交给redux去处理吧。
useMemo
我们来看一个反例:
import React from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return <div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:
export default function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return <div>
<h4>{count}-{expensive}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
上面我们可以看到,使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。
useCallback
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({ callback }) {
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{count}
</div>
}