基于操作

时间:2017-03-30 17:45:59

标签: javascript reactjs animation redux

或多或少地完成了我的第一个React + Redux应用程序,我已经到了我想将动画应用于应用程序的各个部分的程度。看看现有的解决方案,我发现没有什么比我想要的更接近了。整个ReactCSSTransitionGroup似乎都是处理动画的一种侵入性和天真的方式。动画问题会使您想要制作动画的组件流失,而您无法了解应用程序中发生的任何事情。从我最初的分析开始,我已经提出了以下关于我认为是一个好的动画API的要求:

  • 父组件应该不知道子组件如何淡入/淡出(可能除了交错的动画之外)。
  • 动画应该与React集成,以便组件在被删除之前可以淡出(或以其他方式完成动画)。
  • 应该可以在不修改组件的情况下将动画应用于组件。可以将组件设置为与动画兼容,但不应该有与动画相关的道具,状态,上下文或组件,动画也不应该指示如何创建组件。
  • 应该可以根据动作和应用程序状态执行动画 - 换句话说,当我拥有所发生事件的完整语义上下文时。例如,我可能会在创建事物时淡入组件,但不会在页面加载项目时淡入。或者,我可以根据用户的设置选择适当的淡出动画。
  • 应该可以为组件排队或组合动画。
  • 应该可以使用父组件将动画排入队列。例如,如果一个组件有两个子组件,那么打开一个组件会先打开另一个组件,然后再打开它。

排队动画的细节可以由现有的动画库处理,但应该可以将它与react和redux系统联系起来。

我尝试过的一种方法是创建一个像这样的装饰器函数(它是TypeScript,但我不认为这个问题太重要了):

export function slideDown<T>(Component: T) {
  return class FadesUp extends React.Component<any, any> {
        private options = { duration: 0.3 };

        public componentWillEnter (callback) {
            const el = findDOMNode(this).childNodes[0] as Element;
            if (!el.classList.contains("animated-element")) {
                el.classList.add("animated-element");
            }

            TweenLite.set(el, { y: -el.clientHeight });    
            TweenLite.to(el, this.options.duration, {y: 0, ease: Cubic.easeIn, onComplete: callback });
        }

        public componentWillLeave (callback) {
            const el = findDOMNode(this).childNodes[0] as Element;
            if (!el.classList.contains("animated-element")) {
                el.classList.add("animated-element");
            }

            TweenLite.to(el, this.options.duration, {y: -el.clientHeight, ease: Cubic.easeIn, onComplete: callback});   
        }

        public render () {
            const Comp = Component as any;
            return <div style={{ overflow: "hidden", padding: 5, paddingTop: 0}}><Comp ref="child" {...this.props} /></div>;
        }

    } as any;
}

......可以像这样应用......

@popIn
export class Term extends React.PureComponent<ITermStateProps & ITermDispatchProps, void> {
    public render(): JSX.Element {
        const { term, isSelected, onSelectTerm } = this.props;
        return <ListItem rightIcon={<PendingReviewIndicator termId={term.id} />} style={isSelected ? { backgroundColor: "#ddd" } : {}} onClick={onSelectTerm}>{term.canonicalName}</ListItem>;
    }
}

不幸的是,它需要将组件定义为类,但它确实可以声明性地将动画添加到组件而无需修改它。我喜欢这种方法,但讨厌我必须将组件包装在转换组中 - 它也不会解决任何其他要求。

我对React和Redux的内部和扩展点知之甚少,不知道如何处理这个问题。我认为thunk动作是管理动画流程的好地方,但我不想将动作组件发送到动作中。相反,我希望能够检索动作的源组件或类似的东西。另一个角度可以是一个专门的减速器,它传递动作和源组件,允许你以某种方式匹配它们并安排动画。

所以我猜我所追求的是以下一项或多项:

  • 如何挂钩React和/或Redux,最好不要破坏性能或违反库的基本假设。
  • 是否有任何现有的库可以解决部分或全部这些问题,并且很容易集成到应用程序中。
  • 通过使用普通动画工具或集成到正常构建块中来实现所有或大部分目标的技术或方法。

1 个答案:

答案 0 :(得分:2)

我希望我理解一切正确......处理React + Redux意味着,在最好的情况下,您的组件是纯粹的功能。所以应该动画的组件应该(恕我直言)至少采用一个参数:p,它代表动画的状态。 p应位于[0,1]区间内,零代表开始,1代表结束,其间的一切代表当前进度。

const Accordion = ({p}) => {
  return (
    …list of items, each getting p
  );
}

所以问题是,在动画开始之后,如何在某个事件触发该过程之后,动画开始之前如何分派动作(什么是异步事物)。

Middleware在这里派上用场,因为它可以“处理”已分派的动作,将其转换为另一个动作,或转换为多个

//middleware/animatror.js

const animator = store => next => action => {
  if (action.type === 'animator/start') {

    //retrieve animation settings 
    const { duration, start, … } = action.payload;

    animationEngine.add({
      dispatch,
      action: {
       progress: () => { … },
       start: () => { … },
       end: () => { … }
      }
    })
  } else {
    return next(action);
  }
}

export default animator;

因此animationEngineAnimatoreEngine的实例,一个侦听window.requestAnimationFrame事件并调度适当操作的Object。可以使用中间件的创建来实例化animationEngine

const createAnimationMiddleware = () => {
  const animatoreEngine = new AnimatorEngine;

  return const animator = store => next => action => { … }

}

export default createAnimationMiddleware;

//store.js
const animatorMiddleware = createAnimationMiddleware();

…

const store = createStore(
  …,
 applyMiddleware(animatorMiddleware, …)
)

基本的想法是,“吞噬”类型animator/start或其他类型的动作,并将它们转换为一堆“子动作”,这些动作在action.payload中配置。

在中间件中,您可以访问dispatchaction,因此您可以从那里调度其他操作,也可以使用progress参数调用这些操作。

这里显示的代码远未完成,但试图弄清楚这个想法。我有构建»远程«中间件,它处理我的所有请求。如果操作类型为get:/some/path,则它基本上触发启动操作,该操作在有效负载中定义,依此类推。在动作中它看起来像这样:

const modulesOnSuccessAction => data {
  return {
    type: 'modules/listing-success',
    payload: { data }
  }
}

const modulesgetListing = id => dispatch({
  type: `get:/listing/${id}`,
  payload: {
    actions: {
      start: () => {},
      …
      success: data => modulesOnSuccessAction(data)
    }
  }
});

export { getListing }

所以我希望我可以传输Idea,即使代码还没有准备好复制/粘贴。