--- tags: Example, disqus: hackmd --- # React - 留言板2 基本上是根據[React - 留言板](https://hackmd.io/pAypxgRBTzKBaN7m2X4Xug)來延續下去的。 主要為新增,編輯訊息、刪除訊息、以及將訊息保留畫面重整不遺失的功能。 因為前一張的資料結構關係,在編輯訊息時遇到同樣為送出按鈕,但是要如何克服我是要找到父留言的訊息還是子留言的訊息。 ![我的留言資料結構畫面](https://i.imgur.com/QgoAtR1.png) 我的子留言的data是放在父留言的`responseArray`底下的。 因為我在新增子留言的時候直接把子留言的data放到父的`responseArray`, 這樣做導致我在編輯或刪除的時候必須去多傳一個props去判斷現在是父還是子要編輯或刪除。 ### 理想改動 父留言的data結構一樣維持本來的樣子,但是子留言在新增時不是直接塞到父留言的`responseArray`,而是跟父留言一樣只是要多一個欄位這個欄位要放父留言的`id` ```javascript= id: 1001, name: chris, time: '2020-07-10', text: 'hello' id: 2001, parentId: 1001 name: chris, time: '2020-07-10', text: 'hello' ``` 類似這樣的做法,然後再透過處理陣列把有`parentId`的留言放到對應的父留言的`responseArray`裡。 每次新增或刪除,即可以找到對應的父id然後重新把剩下對應到的子留言放入。 --- 附上我目前在留言板2的更動 Home.js ```javascript= import React from 'react'; import { connect } from 'react-redux'; import { addMessage } from '../redux/action/message'; import styled from 'styled-components'; import InputBox from '../component/InputBox'; import MessageContainer from '../component/MessageContainer'; class Home extends React.Component { constructor(props) { super(props); this.props = props; this.handleSumbitMessage = this.handleSumbitMessage.bind(this); } handleSumbitMessage(e) { const { addMessage, messageList } = this.props; addMessage([...messageList, { id: Date.now(), ...e, }]); } componentDidMount() { const { addMessage } = this.props; const messageList = JSON.parse(localStorage.getItem('messageData')); messageList && addMessage(messageList); } render(){ const {messageList} = this.props; return( <HomeWrap> <Title> React 留言板 </Title> <InputBox onSubmitMessage={this.handleSumbitMessage} /> <MessageContainer messageList={messageList} /> </HomeWrap> ); } } const Title = styled.h1` margin: 0 auto 15px; color: #0CA0F9; text-align: center; `; const HomeWrap = styled.div` background: #BEDADB; margin: 0 auto; padding: 15px; width: 700px; `; const mapStateToProps = (state) => ({ messageList: state.messageList, }); const mapDispatchToProps = (dispatch) => ({ addMessage: (e) => { dispatch(addMessage(e)); }, }); export default connect( mapStateToProps, mapDispatchToProps, )(Home); ``` reducer -> message.js ```javascript= import { combineReducers } from 'redux'; import {ADD_MESSAGE} from '../action/message'; const defaultMessage = { id: 0, name: 'chris', text: '測試一號', time: '1990-07-8 12:00:00', responseArray: [], } function messageList(state = [defaultMessage], action) { switch (action.type) { case ADD_MESSAGE: localStorage.setItem('messageData', JSON.stringify(action.info)); return action.info; default: return state; } } const rootReducer = combineReducers({ messageList, }); export default rootReducer; ``` InputBox.js ```javascript= import React from 'react'; import styled from 'styled-components'; export default class InputBox extends React.Component { constructor(props) { super(props); this.props = props; this.state = { name: '', text: '', }; this.handleTextChange = this.handleTextChange.bind(this); this.handleNameChange = this.handleNameChange.bind(this); this.handleGetTime = this.handleGetTime.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleTextChange(e) { this.setState({text: e.currentTarget.value}); } handleNameChange(e) { this.setState({name: e.currentTarget.value}); } handleGetTime(fmt) { const date = new Date(); const o = { "M+": date.getMonth() + 1, //月份 "d+": date.getDate(), //日 "h+": date.getHours(), //小時 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 "q+": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } handleSubmit() { const {isShowInputBox, onSubmitMessage, isReset, isChild, id} = this.props; const {name, text} = this.state; if (!text) { return; } onSubmitMessage({ name: name || '匿名人士', text, time: this.handleGetTime('yyyy-MM-dd hh:mm:ss'), responseArray: [], }, isChild, id); // isReset,按下送出後需要將name跟text清空,除了編輯訊息 if (isReset) { this.setState({ name: '', text: '' }); } // 控制編輯訊息的輸入框狀態 isShowInputBox && isShowInputBox(); } componentDidMount() { this.setState({ name: this.props.propsName || '', text: this.props.propsValue || '', }); } render() { const {propsName} = this.props; const {name, text} = this.state; return ( <InputBoxWrap> <Textarea placeholder="在這留下你想說的話" value={text} onChange={this.handleTextChange} /> <Nickname type="text" placeholder="您的大名" value={name} onChange={this.handleNameChange} disabled={propsName} /> <Submit onClick={this.handleSubmit}>確認送出</Submit> </InputBoxWrap> ); } } InputBox.defaultProps = { isReset: true, isChild: false, }; const Submit = styled.button` border: none; border-radius: 5px; height: 30px; cursor: pointer; transition: all .5s ease; user-select: none; &:focus { outline: none; } &:hover { background: #FEBFCF; } `; const Nickname = styled.input` margin-right: 20px; border: none; border-radius: 5px; padding: 0 10px; width: 150px; height: 30px; box-sizing: border-box; &:focus { outline: none; } `; const Textarea = styled.textarea` border: none; border-radius: 5px; padding: 10px; width: 100%; height: 200px; font-size: 20px; resize: none; box-sizing: border-box; &:focus { outline: none; } `; const InputBoxWrap = styled.div` margin: 0 auto 20px; width: 500px; max-width: 100%; `; ``` MessageContainer.js ```javascript= import React from 'react'; import styled from 'styled-components'; import MessageItem from '../component/MessageItem'; export default class MessageContainer extends React.Component { constructor(props) { super(props); this.props = props; } render() { const {messageList} = this.props; return ( <MessageWrap> { messageList.map((element, index) => { return <MessageItem key={index} id={element.id} name={element.name} value={element.text} time={element.time} responseArray={element.responseArray} /> }) } </MessageWrap> ); } } const MessageWrap = styled.div` margin: 0 auto; padding: 10px; width: 500px; min-height: 300px; `; ``` MessageItem.js ```javascript= import React from 'react'; import { connect } from 'react-redux'; import { addMessage } from '../redux/action/message'; import styled from 'styled-components'; import InputBox from '../component/InputBox'; import MessageAtom from '../component/MessageAtom'; class MessageItem extends React.Component { constructor(props) { super(props); this.props = props; this.state = { isShowResponse: false, } this.handleSumbitMessage = this.handleSumbitMessage.bind(this); this.handleToggleResponse = this.handleToggleResponse.bind(this); this.handleEditMessage = this.handleEditMessage.bind(this); } handleSumbitMessage(e) { const { addMessage, messageList, id } = this.props; const cloneMessageList = Object.assign([], messageList); cloneMessageList.find((element) => { if (id === element.id) { element.responseArray = [ ...element.responseArray, { id: Date.now(), name: e.name, text: e.text, time: e.time, } ] } }); addMessage(cloneMessageList); } handleToggleResponse() { const {isShowResponse} = this.state; this.setState({isShowResponse: !isShowResponse}); } handleEditMessage(e, isChild, id) { const { addMessage, messageList } = this.props; const cloneMessageList = Object.assign([], messageList); if (isChild) { cloneMessageList.map(list => { list.responseArray.find(childList => { if (id === childList.id) { childList.text = e.text; childList.time = e.time; } }); }); } else { cloneMessageList.find(list => { if (id === list.id) { list.text = e.text; list.time = e.time; } }); } addMessage(cloneMessageList); } render(){ const {responseArray, name, time, value, id} = this.props const {isShowResponse} = this.state; return ( <MessageItemWrap> <MessageAtom id={id} name={name} time={time} value={value} onSubmitMessage={this.handleEditMessage} /> { responseArray && responseArray.map((element, index) => { return ( <ResponseMessage key={index}> <MessageAtom id={element.id} name={element.name} time={element.time} value={element.text} onSubmitMessage={this.handleEditMessage} isChild={true} /> </ResponseMessage> ); }) } <ResponseButton isShow={isShowResponse} onClick={this.handleToggleResponse}>發表回應</ResponseButton> { isShowResponse && <InputBox isShowInputBox={this.handleToggleResponse} onSubmitMessage={this.handleSumbitMessage} /> } </MessageItemWrap> ); } } const mapStateToProps = (state) => ({ messageList: state.messageList, }); const mapDispatchToProps = (dispatch) => ({ addMessage: (e) => { dispatch(addMessage(e)); }, }); export default connect( mapStateToProps, mapDispatchToProps, )(MessageItem); const ResponseButton = styled.div` display: inline-block; margin-bottom: ${props => props.isShow && '15px'}; max-width: 80px; height: 30px; cursor: pointer; color: #00A0E9; font-size: 14px; line-height: 30px; user-select: none; &:hover { color: #37C0FF; } `; const ResponseMessage = styled.div` margin: 5px 0 0 40px; `; const MessageItemWrap = styled.div` margin-bottom: 15px; padding: 15px; border-radius: 5px; background: #E6E6E6; width: 500px; box-sizing: border-box; p { margin: 0; } `; ``` MessageAtom.js ```javascript= import React from 'react'; import { connect } from 'react-redux'; import { addMessage } from '../redux/action/message'; import styled from 'styled-components'; import InputBox from '../component/InputBox'; class MessageAtom extends React.Component { constructor(props) { super(props); this.props = props; this.state = { isShowInputBox: false, } this.handleShowInputBox = this.handleShowInputBox.bind(this); this.handleDeleteMessage = this.handleDeleteMessage.bind(this); } handleShowInputBox() { const {isShowInputBox} = this.state; this.setState({isShowInputBox: !isShowInputBox}); } handleDeleteMessage() { const {addMessage, messageList, id, isChild} = this.props; const cloneMessageList = Object.assign([], messageList); if (isChild) { cloneMessageList.map((list, index) => { list.responseArray.find((childList, childIndex) => { if (id === childList.id) { cloneMessageList[index].responseArray.splice(childIndex, 1); addMessage(cloneMessageList); return childList; } }); }); } else { cloneMessageList.find((list, index) => { if (id === list.id) { cloneMessageList.splice(index, 1); addMessage(cloneMessageList); return list; } }); } } render() { const {onSubmitMessage, id, name, time, value, isChild} = this.props; const {isShowInputBox} = this.state; return ( <> <Info><span>{name}</span> 在 {time} 發佈了這則訊息</Info> <Message>{value}</Message> <HandleWrap> <HandleButton onClick={this.handleShowInputBox}>編輯訊息</HandleButton> <HandleButton onClick={this.handleDeleteMessage}>刪除訊息</HandleButton> </HandleWrap> { isShowInputBox && <InputBox isShowInputBox={this.handleShowInputBox} onSubmitMessage={onSubmitMessage} id={id} propsName={name} propsValue={value} isChild={isChild} isReset={false} /> } </> ); } } const mapStateToProps = (state) => ({ messageList: state.messageList, }); const mapDispatchToProps = (dispatch) => ({ addMessage: (e) => { dispatch(addMessage(e)); }, }); export default connect( mapStateToProps, mapDispatchToProps, )(MessageAtom); const HandleButton = styled.div` margin-left: 10px; display: inline-block; max-width: 80px; height: 30px; cursor: pointer; color: #00A0E9; font-size: 14px; line-height: 30px; user-select: none; &:hover { color: #E90000; } `; const HandleWrap = styled.div` text-align: right; `; const Message = styled.pre` margin: 0; padding: 10px 0; color: #303233; font-size: 16px; `; const Info = styled.p` color: #666; font-size: 14px; span { color: #00A0E9; font-weight: bold; } `; ``` InputBox.js ```javascript= import React from 'react'; import styled from 'styled-components'; export default class InputBox extends React.Component { constructor(props) { super(props); this.props = props; this.state = { name: '', text: '', }; this.handleTextChange = this.handleTextChange.bind(this); this.handleNameChange = this.handleNameChange.bind(this); this.handleGetTime = this.handleGetTime.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleTextChange(e) { this.setState({text: e.currentTarget.value}); } handleNameChange(e) { this.setState({name: e.currentTarget.value}); } handleGetTime(fmt) { const date = new Date(); const o = { "M+": date.getMonth() + 1, //月份 "d+": date.getDate(), //日 "h+": date.getHours(), //小時 "m+": date.getMinutes(), //分 "s+": date.getSeconds(), //秒 "q+": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } handleSubmit() { const {isShowInputBox, onSubmitMessage, isReset, isChild, id} = this.props; const {name, text} = this.state; if (!text) { return; } onSubmitMessage({ name: name || '匿名人士', text, time: this.handleGetTime('yyyy-MM-dd hh:mm:ss'), responseArray: [], }, isChild, id); // isReset,按下送出後需要將name跟text清空,除了編輯訊息 if (isReset) { this.setState({ name: '', text: '' }); } // 控制編輯訊息的輸入框狀態 isShowInputBox && isShowInputBox(); } componentDidMount() { this.setState({ name: this.props.propsName || '', text: this.props.propsValue || '', }); } render() { const {propsName} = this.props; const {name, text} = this.state; return ( <InputBoxWrap> <Textarea placeholder="在這留下你想說的話" value={text} onChange={this.handleTextChange} /> <Nickname type="text" placeholder="您的大名" value={name} onChange={this.handleNameChange} disabled={propsName} /> <Submit onClick={this.handleSubmit}>確認送出</Submit> </InputBoxWrap> ); } } InputBox.defaultProps = { isReset: true, isChild: false, }; const Submit = styled.button` border: none; border-radius: 5px; height: 30px; cursor: pointer; transition: all .5s ease; user-select: none; &:focus { outline: none; } &:hover { background: #FEBFCF; } `; const Nickname = styled.input` margin-right: 20px; border: none; border-radius: 5px; padding: 0 10px; width: 150px; height: 30px; box-sizing: border-box; &:focus { outline: none; } `; const Textarea = styled.textarea` border: none; border-radius: 5px; padding: 10px; width: 100%; height: 200px; font-size: 20px; resize: none; box-sizing: border-box; &:focus { outline: none; } `; const InputBoxWrap = styled.div` margin: 0 auto 20px; width: 500px; max-width: 100%; `; ```