react-native开发

react hooks

2021-01-20  本文已影响0人  朱传武

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内的函数什么时候执行,例如:

Jan-20-2021 01-07-16.gif

右上角的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>
}
上一篇 下一篇

猜你喜欢

热点阅读