我在React / Redux中创建了一个Web应用程序,而且我还是该框架的新手。到目前为止,我已经创建了一个SignIn
视图组件,该组件将调用signInUser
操作创建者。当我的thunk
文件中的AJAX调用被解析/拒绝时,此操作使用user-actions.js
来分派相应的操作。
另外,我已经创建了一个Notification
视图组件。我有一个addNotification
操作,notificationReducer
可以使用它。除了处理用户登录的通知外,这可能会收到通知。
当AJAX请求被拒绝时,我希望Notification
组件更新并显示错误。当signInUser
操作被拒绝时,它会调用addNotification
文件中的notification-actions.js
操作。 notificationReducer
更新状态,Notification
组件显示通知。
这是构建操作和组件之间通信的好方法吗?我应该考虑更好的做法吗?
(注意:我已经在我的项目中使用react,redux,react-router,thunk。我想避免使用另一个库,因此我无法更好地理解如何设置React和Redux之间正确的事情。)
答案 0 :(得分:2)
是的,这是首选方式。大约一年前,我创造了与此类似的东西。这就是我设置它的方式..
首先要注意的事情是..这是在打字稿中,所以你需要删除类型声明:)
我使用npm包lodash
进行操作,使用classnames
(cx别名)进行内联类名称赋值。
此设置的优点是我在操作创建时为每个通知使用唯一标识符。 (例如notify_id)。此唯一ID为Symbol()
。这样,如果您想在任何时间删除任何通知,您可以知道要删除哪一个。此通知系统将允许您根据需要进行堆叠,并且在动画完成时它们将消失。我正在挂钩动画事件,当它完成时,我触发一些代码来删除通知。我还设置了一个回退超时来删除通知,只是动画回调没有触发。
import { USER_SYSTEM_NOTIFICATION } from '../constants/action-types';
interface IDispatchType {
type: string;
payload?: any;
remove?: Symbol;
}
export const notifySuccess = (message: any, duration?: number) => {
return (dispatch: Function) => {
dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: true, message, notify_id: Symbol(), duration } } as IDispatchType);
};
};
export const notifyFailure = (message: any, duration?: number) => {
return (dispatch: Function) => {
dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: false, message, notify_id: Symbol(), duration } } as IDispatchType);
};
};
export const clearNotification = (notifyId: Symbol) => {
return (dispatch: Function) => {
dispatch({ type: USER_SYSTEM_NOTIFICATION, remove: notifyId } as IDispatchType);
};
};
const defaultState = {
userNotifications: []
};
export default (state: ISystemNotificationReducer = defaultState, action: IDispatchType) => {
switch (action.type) {
case USER_SYSTEM_NOTIFICATION:
const list: ISystemNotification[] = _.clone(state.userNotifications) || [];
if (_.has(action, 'remove')) {
const key = parseInt(_.findKey(list, (n: ISystemNotification) => n.notify_id === action.remove));
if (key) {
// mutate list and remove the specified item
list.splice(key, 1);
}
} else {
list.push(action.payload);
}
return _.assign({}, state, { userNotifications: list });
}
return state;
};
在您的应用程序的基础渲染中,您将呈现通知
render() {
const { systemNotifications } = this.props;
return (
<div>
<AppHeader />
<div className="user-notify-wrap">
{ _.get(systemNotifications, 'userNotifications') && Boolean(_.get(systemNotifications, 'userNotifications.length'))
? _.reverse(_.map(_.get(systemNotifications, 'userNotifications', []), (n, i) => <UserNotification key={i} data={n} clearNotification={this.props.actions.clearNotification} />))
: null
}
</div>
<div className="content">
{this.props.children}
</div>
</div>
);
}
用户通知类
/*
Simple notification class.
Usage:
<SomeComponent notifySuccess={this.props.notifySuccess} notifyFailure={this.props.notifyFailure} />
these two functions are actions and should be props when the component is connect()ed
call it with either a string or components. optional param of how long to display it (defaults to 5 seconds)
this.props.notifySuccess('it Works!!!', 2);
this.props.notifySuccess(<SomeComponentHere />, 15);
this.props.notifyFailure(<div>You dun goofed</div>);
*/
interface IUserNotifyProps {
data: any;
clearNotification(notifyID: symbol): any;
}
export default class UserNotify extends React.Component<IUserNotifyProps, {}> {
public notifyRef = null;
private timeout = null;
componentDidMount() {
const duration: number = _.get(this.props, 'data.duration', '');
this.notifyRef.style.animationDuration = duration ? `${duration}s` : '5s';
// fallback incase the animation event doesn't fire
const timeoutDuration = (duration * 1000) + 500;
this.timeout = setTimeout(() => {
this.notifyRef.classList.add('hidden');
this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
}, timeoutDuration);
TransitionEvents.addEndEventListener(
this.notifyRef,
this.onAmimationComplete
);
}
componentWillUnmount() {
clearTimeout(this.timeout);
TransitionEvents.removeEndEventListener(
this.notifyRef,
this.onAmimationComplete
);
}
onAmimationComplete = (e) => {
if (_.get(e, 'animationName') === 'fadeInAndOut') {
this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
}
}
handleCloseClick = (e) => {
e.preventDefault();
this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
}
assignNotifyRef = target => this.notifyRef = target;
render() {
const {data, clearNotification} = this.props;
return (
<div ref={this.assignNotifyRef} className={cx('user-notification fade-in-out', {success: data.isSuccess, failure: !data.isSuccess})}>
{!_.isString(data.message) ? data.message : <h3>{data.message}</h3>}
<div className="close-message" onClick={this.handleCloseClick}>+</div>
</div>
);
}
}
@white: #FFFFFF;
@green: #58ba68;
@charcoal: #404040;
@warning-red: #e63c3c;
.user-notify-wrap {
position: fixed;
bottom: 1rem;
left: 1rem;
min-width: 20rem;
z-index: 2000;
.user-notification {
position: relative;
width: 100%;
text-align: center;
color: @white;
background-color: @charcoal;
margin-top: 1rem;
padding: 1rem 2.5rem 1rem 1rem;
border-radius: 3px;
box-shadow: 0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
opacity: 0;
transition: all .5s;
&:first-child {
margin-top: 0;
}
&.success {
background-color: @green;
}
&.failure {
/*background-image: linear-gradient(to right, @faded-red, @purple);*/
background-color: @warning-red;
}
&.fade-in-out {
animation: fadeInAndOut forwards;
}
&.hidden {
height: 0;
margin: 0;
padding: 0;
display: none;
visibility: hidden;
}
.close-message {
position: absolute;
top: 50%;
right: 1rem;
font-size: 2rem;
color: @white;
cursor: pointer;
transform: rotate(45deg) translate(-50%, -50%);
}
* {
color: @white;
font-size: 1.125rem;
}
h1, h2, h3, h4, h5, h6 {
color: @white !important;
margin: 0 !important;
font-family: Lato;
padding: 0 1rem;
}
}
}
@keyframes fadeInAndOut {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
99% { opacity: 0; }
100% { display: none !important; visibility: none !important; height: 0 !important; width: 0 !important; }
}