Taro: 函数式组件作为页面时如何使用页面配置和页面事件处理函数

Created on 16 May 2019  ·  22Comments  ·  Source: NervJS/taro

比如原来的类组件是:

export default class Index extends Component {
  config = {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '首页',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
  }

  render () {
    return (
      <View className='index'>
        <Text>1</Text>
      </View>
    )
  }
}

config 是类的实例属性,不是静态属性,如果这样是不行的:

function Index() {
  return (
      <View className='index'>
        <Text>1</Text>
      </View>
    )
}

Index.config = {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '首页',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
}

export default Index

换成函数式组件应该如何使用页面配置,看了下没有提供 useConfig 钩子,页面事件处理函数onPullDownRefreshonReachBottom等等)也同样面临这个问题,希望能提供一些钩子(usePullDownRefreshuseReachBottom等等)来解决这个问题

Most helpful comment

把所有的回调保存到组件实例貌似是可行的。

编译后的代码样例:

Page({
  data: {
    text: "This is page data."
  },

  __eventHooks: {
    onPullDownRefresh: [
      callback1,
      callback2,
      callback3,
    ]
  },

  onPullDownRefresh: function() {
    this.__eventHooks.onPullDownRefresh.forEach(callback => callback(arguments))
  },
})

taro/hooks.js 的简单实现样例:

// taro/hooks.js
export function usePullDownRefresh (callback) {
  const hook = getHooks(Current.index++);
  if (!hook.component) {
    // 第一次运行此钩子

    // 保存组件实例
    hook.component = Current.current;

    // 设置默认值
    if (hook.component.__eventHooks === undefined) {
      hook.component.__eventHooks = {}
    }

    const eventHooks = hook.component.__eventHooks;

    if (eventHooks.onPullDownRefresh === undefined) {
      eventHooks.onPullDownRefresh = []
      // 记录为第一个使用 usePullDownRefresh 的钩子
      hook.isFirstEventHook = true;
    }

    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback)
  } else {
    if (hook.isFirstEventHook) {
      // 此为第一个使用 usePullDownRefresh 的钩子,清空旧的钩子
      eventHooks.onPullDownRefresh = [];
    }
    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback);
  }
}

使用:

function Page() {
  usePullDownRefresh(() => {
    console.log('pull down refresh 1')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 2')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 3')
  })

  return (
    <View>Test For usePullDownRefresh</View>
  )
}

All 22 comments

欢迎提交 Issue~

如果你提交的是 bug 报告,请务必遵循 Issue 模板的规范,尽量用简洁的语言描述你的问题,最好能提供一个稳定简单的复现。🙏🙏🙏

如果你的信息提供过于模糊或不足,或者已经其他 issue 已经存在相关内容,你的 issue 有可能会被关闭。

Good luck and happy coding~

目前我们的想法是像原生小程序一样引入一个配置 JSON 文件

CC @luckyadam

好的,那页面事件处理函数(onPullDownRefresh、onReachBottom 等)是用钩子(usePullDownRefresh、useReachBottom 等)实现吗

这种情况我觉得就应该用 Class 来写

同样遇到这个问题,react-hooks 无法支撑小程序原生的api

个人觉得页面的 config 应该改为静态属性而不是实例属性,就像组件一样,反正 config 是编译成静态的 json 文件。
这样就可以很方便地像组件一样设置 config:

function Index() {
  return (
      <View className='index'>
        <Text>1</Text>
      </View>
    )
}

Index.config = {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '首页',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
}

export default Index

个人觉得页面的 config 应该改为静态属性而不是实例属性,就像组件一样,反正 config 是编译成静态的 json 文件。
这样就可以很方便地像组件一样设置 config:

function Index() {
  return (
      <View className='index'>
        <Text>1</Text>
      </View>
    )
}

Index.config = {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '首页',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
}

export default Index

同时这也比引入一个json配置文件要直观和易维护。

个人觉得页面的 config 应该改为静态属性而不是实例属性,就像组件一样,反正 config 是编译成静态的 json 文件。
这样就可以很方便地像组件一样设置 config:

function Index() {
  return (
      <View className='index'>
        <Text>1</Text>
      </View>
    )
}

Index.config = {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '首页',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
}

export default Index

同时这也比引入一个json配置文件要直观和易维护。

下个版本就可以支持你这个写法

这种情况我觉得就应该用 Class 来写

如果是这样,Hooks 的使用场景势必会受到很大的限制,希望提供相应的自定义 hooks。

在官方提供支持前,可以像下面这样写,将页面拆成容器和内容两部分,容器组件部分依然使用 Class API 以便使用小程序周期方法,内容组件使用 Hooks API编写。

容器组件

export default class extends Taro.Component {
  state = { value: 0 } 

  onPullDownRefresh() {
    this.setState(c => c + 1)
  }

  render () {
    return (
      <MyPage value={this.state.value}/>
    )
  }
}

内容组件

function MyPage (props) {
    const [value, setValue] = useState(props.value) 
    return <View>{value}</View>
}

@yuche 所以该问题的最终定论是如果设计原生生命周期,例如onPullDownRefresh,只能使用Class ?

@yuche 所以该问题的最终定论是如果设计原生生命周期,例如onPullDownRefresh,只能使用Class ?

hooks 是运行时才能知道有什么 hooks,而原生钩子函数例如 onPullDownRefresh 是要在组件被创建时就要知道挂载函数的内容。这两者的运行机制导致了 hooks 和原生钩子函数是不兼容的。

@yuche 所以该问题的最终定论是如果设计原生生命周期,例如onPullDownRefresh,只能使用Class ?

hooks 是运行时才能知道有什么 hooks,而原生钩子函数例如 onPullDownRefresh 是要在组件被创建时就要知道挂载函数的内容。这两者的运行机制导致了 hooks 和原生钩子函数是不兼容的。

Taro 是否可以为函数组件提前注册原生钩子函数,等到运行时下发对应事件进行处理呢?(下面是我想到的一个示意代码)

// 用户代码
function MyPage (props) {
    const [value, setValue] = useState(props.value) 
    usePullDownRefresh(MyPage, () => {
      setValue(value + 1)
    }, [value])
    return <View>{value}</View>
}


// taro 内部
function usePullDownRefresh (MyPage, cb, deps) {
  useEffect(() => {
    MyPage.addEventlisten('pulldownrefresh', cb)
    return () => {
      MyPage.removeEventlisten('pulldownrefresh', cb)
    }
  }, deps)
}

// Taro 在创建组件时,为组件加上监听事件,当对应事件触发时,下发事件,如 MyPage.dispatch('pulldownrefresh')

把所有的回调保存到组件实例貌似是可行的。

编译后的代码样例:

Page({
  data: {
    text: "This is page data."
  },

  __eventHooks: {
    onPullDownRefresh: [
      callback1,
      callback2,
      callback3,
    ]
  },

  onPullDownRefresh: function() {
    this.__eventHooks.onPullDownRefresh.forEach(callback => callback(arguments))
  },
})

taro/hooks.js 的简单实现样例:

// taro/hooks.js
export function usePullDownRefresh (callback) {
  const hook = getHooks(Current.index++);
  if (!hook.component) {
    // 第一次运行此钩子

    // 保存组件实例
    hook.component = Current.current;

    // 设置默认值
    if (hook.component.__eventHooks === undefined) {
      hook.component.__eventHooks = {}
    }

    const eventHooks = hook.component.__eventHooks;

    if (eventHooks.onPullDownRefresh === undefined) {
      eventHooks.onPullDownRefresh = []
      // 记录为第一个使用 usePullDownRefresh 的钩子
      hook.isFirstEventHook = true;
    }

    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback)
  } else {
    if (hook.isFirstEventHook) {
      // 此为第一个使用 usePullDownRefresh 的钩子,清空旧的钩子
      eventHooks.onPullDownRefresh = [];
    }
    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback);
  }
}

使用:

function Page() {
  usePullDownRefresh(() => {
    console.log('pull down refresh 1')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 2')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 3')
  })

  return (
    <View>Test For usePullDownRefresh</View>
  )
}

有计划支持嘛,随便还想问一下如何获取router

有计划支持嘛,随便还想问一下如何获取router

router似乎还是可以通过this.$router的方式获取,其他的小程序处理函数也可以通过挂到this下面的方式实现,比如要实现分享可以这么写:

function Page(){
  this.onShareAppMessage = function(res){
     console.log(res);
  }
}

不过没有阅读编译代码,不知道会有什么问题。

1.3.14 应该已经支持页面事件处理函数(usePullDownRefresh/useReachBottom 等)的 Hooks 写法了,也可以使用useRouter获取路由了,感谢 Taro 团队。

image

image

把所有的回调保存到组件实例貌似是可行的。

编译后的代码样例:

Page({
  data: {
    text: "This is page data."
  },

  __eventHooks: {
    onPullDownRefresh: [
      callback1,
      callback2,
      callback3,
    ]
  },

  onPullDownRefresh: function() {
    this.__eventHooks.onPullDownRefresh.forEach(callback => callback(arguments))
  },
})

taro/hooks.js 的简单实现样例:

// taro/hooks.js
export function usePullDownRefresh (callback) {
  const hook = getHooks(Current.index++);
  if (!hook.component) {
    // 第一次运行此钩子

    // 保存组件实例
    hook.component = Current.current;

    // 设置默认值
    if (hook.component.__eventHooks === undefined) {
      hook.component.__eventHooks = {}
    }

    const eventHooks = hook.component.__eventHooks;

    if (eventHooks.onPullDownRefresh === undefined) {
      eventHooks.onPullDownRefresh = []
      // 记录为第一个使用 usePullDownRefresh 的钩子
      hook.isFirstEventHook = true;
    }

    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback)
  } else {
    if (hook.isFirstEventHook) {
      // 此为第一个使用 usePullDownRefresh 的钩子,清空旧的钩子
      eventHooks.onPullDownRefresh = [];
    }
    // 在组件中保存此回调
    eventHooks.onPullDownRefresh = eventHooks.onPullDownRefresh.concat(callback);
  }
}

使用:

function Page() {
  usePullDownRefresh(() => {
    console.log('pull down refresh 1')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 2')
  })

  usePullDownRefresh(() => {
    console.log('pull down refresh 3')
  })

  return (
    <View>Test For usePullDownRefresh</View>
  )
}

taro 好像没有支持这种多次定义钩子的实现,不知道是为什么

taro 好像没有支持这种多次定义钩子的实现,不知道是为什么

通过查看 Taro Hooks 实现的源码部分:

具体的源码在 https://github.com/NervJS/taro/blob/master/packages/taro/src/hooks.js#L40-L91

function usePageLifecycle (callback, lifecycle) {
  const hook = getHooks(Current.index++)

  if (!hook.marked) {
    hook.marked = true
    hook.component = Current.current
    hook.callback = callback

    const component = hook.component
    const originalLifecycle = component[lifecycle]

    hook.component[lifecycle] = function () {
      const callback = hook.callback
      originalLifecycle && originalLifecycle.call(component, ...arguments)
      return callback && callback.call(component, ...arguments)
    }
  } else {
    hook.callback = callback
  }
}

export function useDidShow (callback) {
  usePageLifecycle(callback, 'componentDidShow')
}

可以发现 Taro 在第一次运行某一个页面生命周期钩子时是保存了之前的页面生命周期函数,然后重新设置了一遍页面的生命周期函数,会先调用之前保存的页面生命周期函数再调用本次 Hooks 里传递的函数,理论上是支持多次调用的,之前的生命周期函数里已经有回调了,后面的调用依然会保存之前生命周期函数,依然会执行。

可以支持 Page 层面的 onLoad 和 onUnload 生命周期吗,或者将 usePageLifecycle API 暴露出来,允许开发人员根据需求使用相应的生命周期

onLoadonUnloaduseEffect 就可以了:

useEffect(() => {
  console.log('load');
  return => {
    console.log('unload');
  };
}, []);
Was this page helpful?
0 / 5 - 0 ratings