阅读 518

「ReactNaitve」对hooks最佳实践的探索

author: 轩邈

一、hooks介绍

一. useState

  1. eg: const [count, setCount] = useState(0)

  2. 介绍

    (1)有一个参数:默认值, 可以是函数,只在初始渲染时执行一次

    (2)返回一个带有两个元素的数组

    (3)第一个元素是 state 的值, 第二个元素是更新state的函数,每次新的,使用useCallback,可以让它使用上次的函数;函数名随意不一定是set某某某,参数可以是要更新的值或者函数,当是函数时,函数参数是上一次state的值。

 (4) 如果有多个state则根据 useState 的调用顺序来“记住”每个state的状态归属 
 
 (5)这个特性要求Hooks不可以写在if或者switch等可能不执行的代码片段,会导致调用次序不一致
复制代码

二. useLayoutEffect

签名与 useEffect 相同,在DOM变化(layout布局)后同步触发,渲染之(paint绘制)执行 ,适用于用户可见的 DOM 改变。

三. useEffect

componentDidMountcomponentDidUpdate 不同,传递给 useEffect 的函数在DOM变化(layout布局) 和渲染(paint绘制)触发。 这使得它适用于许多常见的 side effects ,例如设置订阅和事件处理程序,因为大多数类型的工作不应阻止浏览器更新(判断标准)屏幕。

1. eg:
  `componentDidMount:  useLayoutEffect(() => {setTitle(1)}, [])`   

  `componentDidUpdate:  useLayoutEffect(() => {console.log(1)})` 

  `componentWillUnmount: useLayoutEffect()=>{return () => {console.log('我要卸载组件啦')}}` 

  我是一个方法,组件更新后返回,下次组件更新前执行:`useLayoutEffect(() => {return () => {console.log(‘我是一个方法,组件更新后返回,下次组件更新前执行')}}, [count, count2])` 
复制代码
2. 介绍:相当于 componentDidMount 、 componentDidUpdate 、componentWillUnmount和 我是一个方法组件更新后返回,下次组件更新前执行
3. 有两个参数
  1. 第一个为函数,默认会在渲染完成后执行一次,如果返回的是一个函数,则返回的函数会在第二个参数数组里面的元素发生变化且渲染完成后执行 
  2. 第二个为一个数组(也可以写成常量等类型,不过不会调用参数一),里面写需要监控的state,当state改变时会调用第一个函数,不改变则不会调用参数一,当数组为空时,只会在最开始调用一次,相当于componentDidMount;不传时,默认监控所有state,相当于componentDidUpdate
复制代码

四. useContext

1. eg:
  const theme = useContext(ThemeContext) 
复制代码
2. 使用 Contex时
  ```javascript
  const ThemeContext = React.createContext();
  const LanguageContext = React.createContext();
  ```

  ```js
  <ThemeContext.Consumer>
      {
          theme => (
              <LanguageContext.Cosumer>
                  language => {
                      //可以使用theme和lanugage了
                  }
              </LanguageContext.Cosumer>
          )
      }
  </ThemeContext.Consumer>
  ```

  两个render props写法,两个嵌套看起来麻烦很多


  使用useContext时 

  ```js
  const theme = useContext(ThemeContext);
  const language = useContext(LanguageContext);
  // 这里就可以用theme和language了
  ```

  接受一个由React.createContext返回的上下文对象,写法简化很多并且不再需要理解render props 
复制代码
3. 有时候会造成意想不到的重新渲染
  ```javascript
  const ThemedPage = () => {
      const theme = useContext(ThemeContext);
      return (
         <div>
              <Header color={theme.color} />
              <Content color={theme.color}/>
              <Footer color={theme.color}/>
         </div>
      );
  };
  ```

  当theme的其他属性(如size等其他非color属性)改变时也会导致界面重新渲染 
复制代码

五. useReducer

1. 介绍:增强版的useState, 小型Redux (机制类似,但是不同组件内部数据认识独立的,)
2. eg:
  ```javascript
  const initialState = { count: 0 } 
  const reducer = function reducer(state, action) { 
    switch (action.type) { 
      case 'reset': 
      	return initialState 
      case 'increment': 
      	return { ...state, count: state.count + 1 } 
      case 'decrement': 
      	return { ...state, count: state.count - 1 } 
      default: 
      	return state 
    }
  }
  ```

  //上面这部分应该写在函数组件外面防止函数一遍遍的创建

  const [count3, count3Dispatch] = useReducer(reducer, initialState) 
复制代码
3. 和useState相比dispatch和reducer只被创建了一次

六. useCallback

1. 介绍:返回值为 memoized 回调函数,第一个参数为一个函数,第二个为一个数组,只有内部元素变化,返回的回调函数才会被重新创建
2. eg:
  ```javascript
  const [count4, setCount4] = useState(0) 
  const counterRef = useRef(count4) 
  
  useEffect(
    () => { 
      counterRef.current = count4 
    }, 
    [count4] 
  )
  
  const incrementCount4 = useCallback(() => setCount4(counterRef.current + 1), []) 
  ```
复制代码
  1. 尽量少用,一般都能用useReducer优化

七. useMemo

介绍:useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)

八. useRef

介绍:useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数(initialValue)。返回的对象将存留在整个组件的生命周期中。

九. useImperativeMethods

  1. useImperativeMethods 自定义使用 ref 时公开给父组件的实例值,方便对函数式组件进行ref操作 使调用内部函数成为可能
  2. 应尽量避免这种代码

十. 自定义Hooks兴起可形成一种约定,代码之间的公用逻辑可以使用useXXX形式的函数

```javascript
// eg:  
const useMountLog = name => {
    useEffect(() => {
        console.log(`${this.name}组件渲染时间->${this.end - this.begin}ms`)
    },[])
}
```
复制代码
// eg: usePrevious 
function usePrevious(value) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}
复制代码

二、环境构建

  1. git clone github.com/facebook/re… (我使用时react版本为16.6.1)
  2. cd react
  3. packages/shared目录下所有文件中的enableHooks = false替换为enableHooks = true
  4. 运行yarn install
  5. 运行yarn build -- --type=RN_OSS
  6. 等运行完毕后,将 build/react-native/ 下的内容替换 项目路径/node_modules/react-native/Libraries/Renderer(我使用时react-native版本为0.57.8)下的内容
  7. 项目中react版本为16.7.0-alpha.2(16.7.0-alpha.0~16.7.0-alpha.2皆可,16.7.0正式版将hooks移除了)

三、原有项目改造

  1. 替换类组件

    // 改造前
    export default class HomeScene extends Component{...}
    // 改造后
    export default forwardRef((props, ref) => {...}) // forwardRef包起来是方便函数组件内部方法调用
    复制代码
  2. 构造函数移除

  3. state状态修改和创建改为useState const [visible, setVisible] = useState(false)

  4. 改造前父组件通过ref引用子组件的方法,改造后使用forwardRef将createRef创建的ref传递到子组件内部,再使用useImperativeMethods将内部方法绑定到传进来的ref上

    useImperativeMethods(ref, () => ({ showModal: this.showModal }), [])
    复制代码
  5. 方法

    // 改造前
    showModal = (from, data, ticket) => {...}
    // 改造后
    this.showModal = (from, data, ticket) => {...} // 推荐使用箭头函数方便函数间调用
    复制代码
  6. render改为return,按state状态变化将原先render前的逻辑移入对应useEffect。

  7. 引入redux-react-hook

    (1)引入StoreContext将根组件包起来

    <StoreContext.Provider value={store}>
    	...
    </StoreContext.Provider>
    复制代码

    (2)```javascript // 改造前 const { name, cityName } = this.props.UserInfo // 改造后 const mapState = React.useCallback(state => state.UserInfo, []) const { name, cityName } = useMappedState(mapState)

    
    (3)每次调用useMappedState都会执行subscribe store。 但是,如果store更新,你的组件将只重新渲染一次。 因此,多次调用useMappedState(例如封装在自定义hooks中)不应该对性能产生很大影响。 如果测试发现性能影响较大,可以尝试返回对象。
    
    复制代码
  8. 引入react-navigation-hooks,需升级react-navigation至最新版(3.1.0)适配。

(1)使用

  ```javaScript
  const { navigate } = useNavigation()
  ```

  与原先的路由管理共用一套路由
复制代码

(2)注意:

  1) 如果项目是用pod管理,该RNGestureHandler.podspec里面路径有问题需要修改。
  
  2) createBottomTabNavigator的第二个参数BottomTabNavigatorConfig的navigationOptions属性改为了defaultNavigationOptions
复制代码

四、注意事项

  1. 原先的组件增强基础设施,如高阶组件和反向集成之类的组件需要进行相应修改以适应新的函数组件。
  2. forwarfRef需要额外关注,因为forwardRef包裹组件后返回的是React节点需要单独处理target.$$typeof === Symbol.for('react.forward_ref')(我暂时是这样处理的)
  3. 需要注意增强组件的name或displayName设置,方便错误定位
  4. eslint-plugin-react-hooks: (1)在每个渲染上以相同的顺序调用 Hooks (不能在循环、判断、switch中使用)。 (2)对 Hooks 的调用要么在 PascalCase 函数内(假设是一个组件),要么是另一个 useSomething 函数(假定为自定义Hook)。
  5. 私以为hooks最大的亮点还是在于逻辑复用的易用性提升上,这个需要我们及时转变思路,习惯函数式组件开发。
  6. 如果inputs为[](useCallback的第二个参数),React每次渲染都将使用同样的函数返回值(不管里面的运算),这时你可以选择在函数组件外声明这个函数,但是React官方并不推荐这样做,hooks的重点就是是允许你保留组件中的所有内容。
  7. 我们对组件增强时,组件的回调一般不需要销毁监听,而且仅需监听一次,这与 DOM 监听不同,因此大部分场景,我们需要利用 useCallback 包裹,并传一个空数组,来保证永远只监听一次,而且不需要在组件销毁时注销这个 callback。
  8. hooks目前还有很多坑,是趋势,可了解,慎用。

原文链接: tech.meicai.cn/detail/81, 也可微信搜索小程序「美菜产品技术团队」,干货满满且每周更新,想学习技术的你不要错过哦。

关注下面的标签,发现更多相似文章
评论