Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
vimcaw opened this issue May 16, 2019 · 22 comments
Closed
Assignees

Comments

@vimcaw
Copy link
Contributor

vimcaw commented May 16, 2019

比如原来的类组件是:

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等等)来解决这个问题

@taro-bot
Copy link

taro-bot bot commented May 16, 2019

欢迎提交 Issue~

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

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

Good luck and happy coding~

@yuche
Copy link
Contributor

yuche commented May 17, 2019

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

@yuche yuche added CLI labels May 17, 2019
@taro-bot taro-bot bot assigned luckyadam and unassigned luckyadam May 17, 2019
@taro-bot
Copy link

taro-bot bot commented May 17, 2019

CC @luckyadam

@vimcaw
Copy link
Contributor Author

vimcaw commented May 17, 2019

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

@yuche
Copy link
Contributor

yuche commented May 17, 2019

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

@baoxiangyang
Copy link

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

@vimcaw
Copy link
Contributor Author

vimcaw commented Jun 4, 2019

个人觉得页面的 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

@timest
Copy link

timest commented Jun 4, 2019

个人觉得页面的 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配置文件要直观和易维护。

@yuche
Copy link
Contributor

yuche commented Jun 4, 2019

close via 08cf9b3

@yuche yuche closed this as completed Jun 4, 2019
@yuche
Copy link
Contributor

yuche commented Jun 4, 2019

个人觉得页面的 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配置文件要直观和易维护。

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

@whinc
Copy link

whinc commented Jun 21, 2019

这种情况我觉得就应该用 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>
}

@liu-dongyu
Copy link

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

@yuche
Copy link
Contributor

yuche commented Jun 24, 2019

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

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

@whinc
Copy link

whinc commented Jun 24, 2019

@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')

@vimcaw
Copy link
Contributor Author

vimcaw commented Jun 24, 2019

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

编译后的代码样例:

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>
  )
}

@lbb00
Copy link

lbb00 commented Jun 25, 2019

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

@ptyz030529
Copy link

ptyz030529 commented Jul 6, 2019

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

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

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

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

@vimcaw
Copy link
Contributor Author

vimcaw commented Aug 22, 2019

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

image

image

@litian
Copy link

litian commented Nov 15, 2019

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

编译后的代码样例:

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 好像没有支持这种多次定义钩子的实现,不知道是为什么

@vimcaw
Copy link
Contributor Author

vimcaw commented Nov 15, 2019

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 里传递的函数,理论上是支持多次调用的,之前的生命周期函数里已经有回调了,后面的调用依然会保存之前生命周期函数,依然会执行。

@azzgo
Copy link

azzgo commented May 8, 2020

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

@vimcaw
Copy link
Contributor Author

vimcaw commented May 11, 2020

onLoadonUnloaduseEffect 就可以了:

useEffect(() => {
  console.log('load');
  return => {
    console.log('unload');
  };
}, []);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests