Flex懒惰绑定

时间:2012-03-30 14:26:25

标签: flex binding lazy-loading

受Hibernate的延迟加载能力的启发,我希望仅在必要时使模型成为我的Flex UI请求数据的一部分。我认为这就像添加一个只在访问变量时才发送服务器请求的公共访问器一样简单。

public function get tab2AC():ArrayCollection
{
    if(_tab2AC == null){
        //Request data from server
    }
    return _tab2AC;
}

问题是Flex似乎在应用程序启动时访问所有绑定变量,即使尚未创建引用组件。因此,即使尚未创建带有dataProvider="{tab2AC}"的DataGrid,服务器请求仍然会消失,从而打败“仅在需要时”懒惰。

我不想将服务器请求放在creationComplete处理程序中,因为我想让我的UI模型不知道视图状态,而且我的视图不知道服务器请求。

有趣的是,如果我在访问者中添加Alert.show("anything");,它可以根据需要运行。

更新:这是一个完整的例子。设置断点,你会看到Flex访问这两个变量,即使任何创建的组件都没有使用titleForScreen2。

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }                   
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label text="{titleForScreen1}"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label text="{titleForScreen2}"/>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>

6 个答案:

答案 0 :(得分:2)

flex中的绑定非常愚蠢。更多的概念验证而不是实际优化的生产质量特征。更糟糕的是,如果没有修改编译器,你可以做很少的事情而不需要在getter中使用各种验证逻辑,或者(或许更有可能)某种拦截层,以确保只在UI时才进行昂贵的调用国家是有意义的。但是,在这一点上,您可以完全取消绑定,并为被动视图实现一个活动控制器。

我知道这是一个非常蹩脚的答案,但这是真的。多年来,我一直是一名灵活开发人员,与其绑定功能的关系也很长。同样,在所有这些时间里,绑定实现中唯一发生变化的是能够进行双向绑定。


顺便说一句,从语法上讲,我会使用常规方法而不是属性来返回promise。属性通常被视为同步和廉价(-ish)操作,而方法(尤其是返回promise的方法)将具有更灵活的内涵。

答案 1 :(得分:1)

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            private var _titleForScreen1:String;
            private var _titleForScreen2:String;

            public function get titleForScreen1():String {
                if(_titleForScreen1 == null){
                    //Server Request
                }                   
                return _titleForScreen1;
            }

            public function get titleForScreen2():String {
                Alert.show("test");
                if(_titleForScreen2 == null){
                    //Server Request
                }
                return _titleForScreen2;
            }
        ]]>
    </fx:Script>

    <mx:ViewStack>
        <s:NavigatorContent label="Screen 1">
            <s:Label text="{titleForScreen1}"/>
        </s:NavigatorContent>
        <s:NavigatorContent label="Screen 2">
            <s:Label text="{titleForScreen2}"/>
        </s:NavigatorContent>
    </mx:ViewStack>
</s:WindowedApplication>

第12行和第19行的断点,在每个断点期间查看堆栈跟踪,也可以弹出Binding并查看wrapFunctionCall(也在那里删除断点)。因此,当它到达第12行和第19行时,由于预加载器调度完整事件,它会击中它们2次。我在每个文件中放置了断点,堆栈跟踪显示执行路径通过。不幸的是我无法找到引起2次调用的地方(必须在我没有源代码的部分)它似乎跟踪中的每个点只被调用一次,但我认为wrapFunctionCall在两次调用期间被调用了两次这两次处决的时期。发生的第三个问题是由于调用了doPhasedInstantation,它调用了对所有拥有systemManager的子进程的Bindings执行,所以看起来组件有一个系统管理器,即使它们可能尚未添加到系统管理器中。舞台或创造。对不起,我没有更具体的答案,为什么每一个都必须发生,但我猜是有一些很好的理由。

啊,你差点忘了,你也会看到警告显示的时候会导致错误,但是在Binding.as中的wrappedFuncitonCall方法中捕获了该错误

catch(error:Error)
    {
        // Certain errors are normal when executing a srcFunc or destFunc,
        // so we swallow them:
        //   Error #1006: Call attempted on an object that is not a function.
        //   Error #1009: null has no properties.
        //   Error #1010: undefined has no properties.
        //   Error #1055: - has no properties.
        //   Error #1069: Property - not found on - and there is no default value
        // We allow any other errors to be thrown.
        if ((error.errorID != 1006) &&
            (error.errorID != 1009) &&
            (error.errorID != 1010) &&
            (error.errorID != 1055) &&
            (error.errorID != 1069))
        {
            throw error;
        }
        else
        {
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + error);
            }
        }
    }

答案 2 :(得分:0)

您的陈述不正确,启动时Flex应用程序无法访问tab2AC getter,因为此处的证据是完整的应用程序代码:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            private var _tab2AC:ArrayCollection;

            public function set tab2AC(value:ArrayCollection):void
            {
                _tab2AC = value;
            }

            [Bindable]
            public function get tab2AC():ArrayCollection
            {
                if(_tab2AC == null){
                    trace("THIS WILL NOT BE CALLED");
                }
                return _tab2AC;
            }

        ]]>
    </fx:Script>

</s:Application>

正如您所看到的,跟踪不会被触发,因此您的问题似乎来自于从应用程序某处调用该getter,找到它,设置断点然后“Step return”需要的。

话虽这么说,你不应该直接在getter中以这种方式实现延迟加载,因为服务调用是异步的。

答案 3 :(得分:0)

为什么不在变量为null时返回新的ArrayCollection,然后在服务器调用返回时在ArrayCollection上设置源?

答案 4 :(得分:0)

我认为这只是调试器的不稳定行为,而不是普通执行中会发生什么。如果您在其中放置任何逻辑,这将使您能够确定调用的函数不与调试器绑定(例如在标签上设置文本),那么getter不会被叫。但是,如果在其中放置trace语句,则会调用getter

难题是你想要多少依赖于这只会在调试中发生的想法,以及在使用调试播放器时获得真实行为有多重要?

答案 5 :(得分:0)

所以是的,那就是它的方式。由于Flex会立即评估绑定,因此我必须延迟绑定直到创建,以防止过早评估。看起来像撤消Flex的奇怪行为的额外工作,但这有时是如何发生的。

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
    <![CDATA[
        import mx.binding.utils.BindingUtils;
        import mx.binding.utils.ChangeWatcher;

        private var _titleForScreen1:String;
        private var _titleForScreen2:String;

        public function get titleForScreen1():String {
            if(_titleForScreen1 == null){
                //Server Request
            }
            return _titleForScreen1;
        }

        public function get titleForScreen2():String {
            if(_titleForScreen2 == null){
                //Server Request
            }
            return _titleForScreen2;
        }

        public function updateLabel1(value:String):void {screen1Label.text = value;}
        public function updateLabel2(value:String):void {screen2Label.text = value;}

        public function bindLabel1():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel1,this, "titleForScreen1");
        }

        public function bindLabel2():void {
            var changeWatcher:ChangeWatcher = BindingUtils.bindSetter(updateLabel2,this, "titleForScreen2");
        }
    ]]>
</fx:Script>

<mx:ViewStack>
    <s:NavigatorContent label="Screen 1">
        <s:Label id="screen1Label" creationComplete="bindLabel1()"/>
    </s:NavigatorContent>
    <s:NavigatorContent label="Screen 2">
        <s:Label id="screen2Label" creationComplete="bindLabel2()"/>
    </s:NavigatorContent>
    </s:NavigatorContent>
</mx:ViewStack>
</s:Application>