fullstackReact 学习笔记 redux
一、使用redux库来更新chat app
1、使用redux中的createStore创建store代替我们自己的createStore函数
import { createStore } from 'redux';
const store = createStore(reducer, initialState);
2、更新messages,使其更新平常的应用.
将messages中的每个message变成一个对象办好text,timeStamp和id三个参数。
{
messages:[
{
id:'233',
imestamp:'233',
text:'this is a message'
}
]
}
生成新的message:
使用uuid生成唯一的标示符:import uuid from 'uuid'
使用Date.now()生成当前的时间搓timeStamp
const newMessage = {
text: action.text,
timestamp: Date.now(),
id: uuid.v4(),
};
更新后的chat app如下:
class App extends React.Component {
componentDidMount() {
store.subscribe(() => this.forceUpdate());
}
render() {
const messages = store.getState().messages;
return (
<div className='ui segment'>
<MessageView messages={messages} />
<MessageInput />
</div>
);
}
}
class MessageInput extends React.Component {
state = {
value: '',
};
onChange = (e) => {
this.setState({
value: e.target.value,
})
};
handleSubmit = () => {
store.dispatch({
type: 'ADD_MESSAGE',
text: this.state.value,
});
this.setState({
value: '',
});
};
render() {
return (
<div className='ui input'>
<input
onChange={this.onChange}
value={this.state.value}
type='text'
/>
<button
onClick={this.handleSubmit}
className='ui primary button'
type='submit'
>
Submit
</button>
</div>
);
}
}
class MessageView extends React.Component {
handleClick = (id) => {
store.dispatch({
type: 'DELETE_MESSAGE',
id: id,
});
};
render() {
const messages = this.props.messages.map((message, index) => (
<div
className='comment'
key={index}
onClick={() => this.handleClick(message.id)} // Use `id`
>
<div className='text'> {/* Wrap message data in `div` */}
{message.text}
<span className='metadata'>@{message.timestamp}</span>
</div>
</div>
));
return (
<div className='ui center aligned basic segment'>
<div className='ui comments'>
{messages}
</div>
</div>
);
}
}
更新后的reducer如下:
function reducer(state, action) {
if (action.type === 'ADD_MESSAGE') {
const newMessage = {
text: action.text,
timestamp: Date.now(),
id: uuid.v4(),
};
return {
messages: state.messages.concat(newMessage),
};
} else if (action.type === 'DELETE_MESSAGE') {
return {
messages: state.messages.filter((m) => (
m.id !== action.id
))
};
} else {
return state;
}
}
3、增加一个对话
修改state tree如下:
{
threads[:{
id:'3333',
title:'li lei',
messages:[
{
id:'233',
imestamp:'233',
text:'this is a message'
}
]
{
id:'4444',
title:'hanmeimei',
messages:[
{
id:'444',
imestamp:'25553',
text:'this is another message'
}
]
]
}
更改初始化state
const initialState = {
activeThreadId: '1-fca2', // New state property
threads: [ // Two threads in state
{
id: '1-fca2', // hardcoded pseudo-UUID
title: 'Buzz Aldrin',
messages: [
{ // This thread starts with a single message already
text: 'Twelve minutes to ignition.',
timestamp: Date.now(),
id: uuid.v4(),
},
],
},
{
id: '2-be91',
title: 'Michael Collins',
messages: [],
},
],
};
activeThreadId:标示当前处于活动的thread,显示时仅允许同时显示一个thread
修改app使其支持thread
class App extends React.Component {
componentDidMount() {
store.subscribe(() => this.forceUpdate());
}
render() {
const state = store.getState();
const activeThreadId = state.activeThreadId;
const threads = state.threads;
const activeThread = threads.find((t) => t.id === activeThreadId);
return (
<div className='ui segment'>
<Thread thread={activeThread} />
</div>
);
}
}
class MessageInput extends React.Component {
state = {
value: '',
};
onChange = (e) => {
this.setState({
value: e.target.value,
})
};
handleSubmit = () => {
store.dispatch({
type: 'ADD_MESSAGE',
text: this.state.value,
});
this.setState({
value: '',
});
};
render() {
return (
<div className='ui input'>
<input
onChange={this.onChange}
value={this.state.value}
type='text'
/>
<button
onClick={this.handleSubmit}
className='ui primary button'
type='submit'
>
Submit
</button>
</div>
);
}
}
class Thread extends React.Component {
handleClick = (id) => {
store.dispatch({
type: 'DELETE_MESSAGE',
id: id,
});
};
render() {
const messages = this.props.thread.messages.map((message, index) => (
<div
className='comment'
key={index}
onClick={() => this.handleClick(message.id)}
>
<div className='text'>
{message.text}
<span className='metadata'>@{message.timestamp}</span>
</div>
</div>
));
return (
<div className='ui center aligned basic segment'>
<div className='ui comments'>
{messages}
</div>
<MessageInput />
</div>
);
}
}
增加ThreadTabs
在App中:
class App extends React.Component {
componentDidMount() {
store.subscribe(() => this.forceUpdate());
}
render() {
const state = store.getState();
const activeThreadId = state.activeThreadId;
const threads = state.threads;
const activeThread = threads.find((t) => t.id === activeThreadId);
const tabs = threads.map(t => (
{ // a "tab" object
title: t.title,
active: t.id === activeThreadId,
}
));
return (
<div className='ui segment'>
<ThreadTabs tabs={tabs} />
<Thread thread={activeThread} />
</div>
);
}
}
class ThreadTabs extends React.Component {
render() {
const tabs = this.props.tabs.map((tab, index) => (
<div
key={index}
className={tab.active ? 'active item' : 'item'}
>
{tab.title}
</div>
));
return (
<div className='ui top attached tabular menu'>
{tabs}
</div>
);
}
}
修改reducer使其支持Threads
修改action
对ADD_MESSAGE增加一个threadId属性
{
type: 'ADD_MESSAGE',
text: this.state.value,
threadId: this.props.threadId,
}
修改reducer:
function reducer(state, action) {
if (action.type === 'ADD_MESSAGE') {
const newMessage = {
text: action.text,
timestamp: Date.now(),
id: uuid.v4(),
};
const threadIndex = state.threads.findIndex(
(t) => t.id === action.threadId
);
const oldThread = state.threads[threadIndex];
const newThread = {
...oldThread,
messages: oldThread.messages.concat(newMessage),
};
return {
...state,
threads: [
...state.threads.slice(0, threadIndex),
newThread,
...state.threads.slice(
threadIndex + 1, state.threads.length
),
],
};
} else if (action.type === 'DELETE_MESSAGE') {
return {
messages: state.messages.filter((m) => (
m.id !== action.id
))
};
} else {
return state;
}
}
修改DELETE_MESSAGE处理函数
else if (action.type === 'DELETE_MESSAGE') {
const threadIndex = state.threads.findIndex(
(t) => t.messages.find((m) => (
m.id === action.id
))
);
const oldThread = state.threads[threadIndex];
const newThread = {
...oldThread,
messages: oldThread.messages.filter((m) => (
m.id !== action.id
)),
};
return {
...state,
threads: [
...state.threads.slice(0, threadIndex),
newThread,
...state.threads.slice(
threadIndex + 1, state.threads.length
),
],
};
添加切换thread,当点击tab时候切换到对应的thread
仅需要修改activeThreadId便可。
action:
仅需要将需要激活的threadId传递给reducer
{
type: 'OPEN_THREAD',
id: id,
}
修改reducer
else if (action.type === 'OPEN_THREAD') {
return {
...state,
activeThreadId: action.id,
};
从ThreadTabs组件中dispatch(action),首先修改props中的tabs,使其增加一个id,
const tabs = threads.map(t => (
{
title: t.title,
active: t.id === activeThreadId,
id: t.id,
}
));
修改component,增加一个点击tab的动作
class ThreadTabs extends React.Component {
handleClick = (id) => {
store.dispatch({
type: 'OPEN_THREAD',
id: id,
});
};
render() {
const tabs = this.props.tabs.map((tab, index) => (
<div
key={index}
className={tab.active ? 'active item' : 'item'}
onClick={() => this.handleClick(tab.id)}
>
{tab.title}
</div>
));
return (
<div className='ui top attached tabular menu'>
{tabs}
</div>
);
}
}
上面的reducer很奇怪,将增减信息和激活tab的处理放在了一起,如果随着程序的增大,reducer将变得越来越大,因此我们需要将reducer进行拆分。
4、拆分reducer,将各自需要的信息传递个各自的reducer
function reducer(state, action) {
return {
activeThreadId: activeThreadIdReducer(state.activeThreadId, action),
threads: threadsReducer(state.threads, action),
};
}
function activeThreadIdReducer(state, action) {
if (action.type === 'OPEN_THREAD') {
return action.id;
} else {
return state;
}
}
function threadsReducer(state, action) {
if (action.type === 'ADD_MESSAGE') {
const newMessage = {
text: action.text,
timestamp: Date.now(),
id: uuid.v4(),
};
const threadIndex = state.findIndex(
(t) => t.id === action.threadId
);
const oldThread = state[threadIndex];
const newThread = {
...oldThread,
messages: oldThread.messages.concat(newMessage),
};
return [
...state.slice(0, threadIndex),
newThread,
...state.slice(
threadIndex + 1, state.length
),
];
} else if (action.type === 'DELETE_MESSAGE') {
const threadIndex = state.findIndex(
(t) => t.messages.find((m) => (
m.id === action.id
))
);
const oldThread = state[threadIndex];
const newThread = {
...oldThread,
messages: oldThread.messages.filter((m) => (
m.id !== action.id
)),
};
return [
...state.slice(0, threadIndex),
newThread,
...state.slice(
threadIndex + 1, state.length
),
];
} else {
return state;
}
}
继续拆分ThreadReducer,将MessageReducer分离出来,用于更新messages
function findThreadIndex(threads, action) {
switch (action.type) {
case 'ADD_MESSAGE': {
return threads.findIndex(
(t) => t.id === action.threadId
);
}
case 'DELETE_MESSAGE': {
return threads.findIndex(
(t) => t.messages.find((m) => (
m.id === action.id
))
);
}
}
}
function threadsReducer(state, action) {
switch (action.type) {
case 'ADD_MESSAGE':
case 'DELETE_MESSAGE': {
const threadIndex = findThreadIndex(state, action);
const oldThread = state[threadIndex];
const newThread = {
...oldThread,
messages: messagesReducer(oldThread.messages, action),
};
return [
...state.slice(0, threadIndex),
newThread,
...state.slice(
threadIndex + 1, state.length
),
];
}
default: {
return state;
}
}
}
function messagesReducer(state, action) {
switch (action.type) {
case 'ADD_MESSAGE': {
const newMessage = {
text: action.text,
timestamp: Date.now(),
id: uuid.v4(),
};
return state.concat(newMessage);
}
case 'DELETE_MESSAGE': {
return state.filter(m => m.id !== action.id);
}
default: {
return state;
}
}
}
5、在reducers中定义初始化值
const store = createStore(reducer);
如果不为createStore提供初始值,那么初始值为undifined,我们可以使用参数传递默认值为所有的reducer初始化值,必须保证state是undifined,才会使用默认值。
function reducer(state = {}, action) { return { activeThreadId: activeThreadIdReducer(state.activeThreadId, action), threads: threadsReducer(state.threads, action), }; } function activeThreadIdReducer(state = '1-fca2', action) { if (action.type === 'OPEN_THREAD') { return action.id; } else { return state; } } function threadsReducer(state = [ { id: '1-fca2', title: 'Buzz Aldrin', messages: messagesReducer(undefined, {}), }, { id: '2-be91', title: 'Michael Collins', messages: messagesReducer(undefined, {}), }, ], action) { switch (action.type) { case 'ADD_MESSAGE': case 'DELETE_MESSAGE': { const threadIndex = findThreadIndex(state, action); const oldThread = state[threadIndex]; const newThread = { ...oldThread, messages: messagesReducer(oldThread.messages, action), }; return [ ...state.slice(0, threadIndex), newThread, ...state.slice( threadIndex + 1, state.length ), ]; } default: { return state; } } } function messagesReducer(state = [], action) { switch (action.type) { case 'ADD_MESSAGE': { const newMessage = { text: action.text, timestamp: Date.now(), id: uuid.v4(), }; return state.concat(newMessage); } case 'DELETE_MESSAGE': { return state.filter(m => m.id !== action.id); } default: { return state; } } }
6、使用combineReducers()
const reducer = combineReducers({
activeThreadId: activeThreadIdReducer,
threads: threadsReducer,
});
浙公网安备 33010602011771号