这几天基于react写了一个小demo测试,主要实现的功能是:在输入框中输入文字,点击添加按钮,在下方的表格中会自动添加一行数据,点击删除按钮后,该行数据被删除。
先来看看最后的效果图:
操作前
操作后
可能样式没怎么调,看着有点别扭,表格样式扒的是菜鸟教程上的,觉得还行的也可以用这个样式哦,或者改成自己喜欢的即可,最重要的是功能实现就好。
1.开发环境搭建
这个在这里我们就不细说了,npm及node.js的安装是前提哦!
国内使用 npm 速度很慢,你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ npm config set registry https://registry.npm.taobao.org
create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。
create-react-app 自动创建的项目是基于 Webpack + ES6 。
执行以下命令创建项目:
$ cnpm install -g create-react-app
$ create-react-app my-app
$ cd my-app/
$ npm start
其中my-app就是你自己的项目名字,如果已经创建好项目,按着提示启动项目即可。若是之前就已经创建好了项目,直接进入项目的目录,输入命令:npm start启动项目就OK啦!
在浏览器中打开http://localhost:3000/就是我们项目运行的地址。
创建好的项目目录结构如下:
2.修改src/App.js文件
manifest.json 指定了开始页面 index.html,一切的开始都从这里开始,所以这个是代码执行的源头。
我最后的文件目录如下:
在index.js里面,我们可以将ReactDOM.render做一点改变,如下:
ReactDOM.render(
<App />,
document.getElementById('container')
);
下面的那个id就是我们自己在index.html里定义的div的id,那么主要问题来了,在App.js中,这个逻辑要怎么理清呢?
首先,对于主体结构,我们应该是没有什么疑问的,无非就是input输入框和按钮。要完成我们的需求,需要两个方法,分别是添加和删除。
其次,对于表格中的数据,我们可以通过一个数组去储存它。点击添加按钮时,就向数组中添加一个元素;点击删除按钮时,将该元素从数组中删除。
至此,思路应该还是相对清晰了,那我们来一起看看代码吧:
let i=0;
class App extends React.Component {
constructor(props) {
super(props);
this.add=this.add.bind(this);
this.delete=this.delete.bind(this);
this.state={
lists:[]
}
}
add(){
const lists=this.state.lists;
const info = document.getElementById("add").value;
lists.push({"id": ++i,"value":info});
this.setState({lists:lists})
}
delete(e){
const index=e.target.getAttribute("data-index");
const lists=this.state.lists;
document.getElementById(index).remove();
this.setState({lists:lists})
}
render() {
return (
<div>
<input type="text" id="add"/>
<button onClick={this.add}>添加</button>
<table id="customers">
<tbody>
<tr>
<th>内容</th>
<th>操作</th>
</tr>
{this.state.lists.map((data)=>{
console.log(data);
return <List key={data.id} index={data.id} info={data.value} delete={this.delete}/>
})}
</tbody>
</table>
</div>
)
}
}
export default App;
add()方法中,info是为了获取我们输入的内容,再将其添加到lists数组中,而这里添加id属性,则是为了删除方便,直接获取元素的下标,通过获取其下标移除元素。
3.自定义组件List
它主要传达的就是我每一行的元素,直接上代码:
class List extends React.Component {
render() {
return (
<tr id={this.props.index} className="alt">
<td>
<input type="text" defaultValue={this.props.info} />
</td>
<td>
<button onClick={this.props.delete} data-index={this.props.index}>删除</button>
</td>
</tr>
)
}
}
4.表格样式
最后要解决的就是样式问题啦,当然,大家还是自定义就好了。这个样式可能是有点奇怪,不过觉得看的过去的伙伴还是可以将就一下滴!
嗯嗯,这个样式放在css里,要用的时候直接导进去就好了。
#customers
{
font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
width:30%;
border-collapse:collapse;
}
#customers td, #customers th
{
font-size:1em;
border:1px solid #98bf21;
padding:3px 7px 2px 7px;
}
#customers th
{
font-size:1.1em;
text-align:left;
padding-top:5px;
padding-bottom:4px;
background-color:#A7C942;
color:#ffffff;
}
#customers tr.alt td
{
color:#000000;
background-color:#EAF2D3;
}
到了这里,表面上我们的需求已经得到实现,但事实上,这里面还存在着许多漏洞。细心的小伙伴们会发现,在我们删除元素的时候只是单纯的把元素移除掉了,也就是说,只是页面上看不到了,但实际的元素仍然存在数组中。所以,我们实际上需要删除的是数组里面的元素,通过splice可删除数组中的元素。
接下来,我们将通过Ant Design组件实现点击按钮,增加一行数据的需求,先来看看最后的效果图:
操作前:
操作后:
emm,这个效果看起来确实要比之前的舒服,在项目开始之前,我们需要部署好我们的环境,这个时候除了之前下载的依赖,还需要使用npm或yarn安装:
$ npm install antd --save
或
$ yarn add antd
这里忘了说一句,这里也需要redux的环境部署,所以还需要 npm install redux命令。
下一步需要做什么呢?先看看我们的目录结构,方便梳理我们要做的是什么:
这里我们的文件命名和方法其实也涉及到了redux,但这并不影响我们的理解,简单来说,actions表示的是动作指令,定义事件的state,reducers里定义的函数通过action触发,component中定义的是组件。当然了,他们之间的交互关系,建议大家可以去看看官方文档:
A:目录清楚后,首先看入口文件index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import TodoList from './component/TodoList';
ReactDOM.render(
<TodoList />,
document.getElementById('root')
);
我想,这里就不用多解释了,render一个自定义组件TodoList,然后将其在页面中渲染出来。
B:组件TodoList中我们主要是定义了四个方法,分别是
(1)handleInputChange:获取输入类型和input的值
(2)handleStoreChange:获取最新的数据
(3)handleBtnClick:点击按钮后添加数据
(4)handleItemDelete:删除数据
具体代码如下:
class TodoList extends Component{
constructor(props){
super(props);
// 获取store里的所有state数据
this.state = store.getState();
store.subscribe(this.handleStoreChange)
}
handleInputChange = (e) => {
// 获取输入类型和input的值
// const action = {
// type: 'CHANGE_INPUT_VALUE',
// value: e.target.value
// }
const action = getInputChangeAction(e.target.value);
// 把action传给store
store.dispatch(action);
// store自动传给reducer
}
// reducer返回newState之后,store传递给newState给组件
handleStoreChange = () => {
// 获取最新的数据
this.setState(store.getState());
}
handleBtnClick = () => {
// const action = {
// type: 'ADD_TODO_ITEM'
// };
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete = (index) => {
// const action = {
// type: 'DELETE_TODO_ITEM',
// index: index
// };
const action = getDeleteItemAction(index);
store.dispatch(action);
}
render(){
return (
<div style={{margin:'10px',marginLeft:'10px'}}>
<div>
<Input
value={this.state.inputValue}
placehoder="todo list "
style={{width:'300px'}}
onChange={this.handleInputChange}
/>
<Button
type= "primary"
onClick={this.handleBtnClick}
>提交</Button>
</div>
<List
style={{marginTop:'10px',width:'300px'}}
bordered
dataSource={this.state.list}
renderItem={(item,index) => (
<List.Item>
<span style={{position:'relative'}}>{item}</span>
<Button style={{position:'absolute',right:'8px',top:'8px'}} onClick={this.handleItemDelete} >删除</Button>
</List.Item>
)
}
/>
</div>
)
}
}
export default TodoList;
C:其中,getInputChangeAction、getAddItemAction和getDeleteItemAction三个方法,来源于我们的actionCreator.js中的自定义函数:
export const getAddItemAction = () => ({
type: ADD_TODO_ITEM
});
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value: value
});
export const getDeleteItemAction = (index) => ({
type: DELETE_TODO_ITEM,
index: index
});
D:定义的type是来自我们的actionTypes.js,其实,我们也可以把它合并在一个js里,具体怎么写,还是看个人习惯啦。
export const ADD_TODO_ITEM = 'add_todo_item';
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const DELETE_TODO_ITEM = 'delete_todo_item';
E:最后要说的是在TodoList中我们用到的store是来源于reducers文件夹下的index.js封装的store。
const store = createStore(
reducer
);
export default store;
F:reducers文件夹下的reducer设置的是一个默认状态
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from '../actions/actionTypes';
const defaultState = {
inputValue: '',
list: []
};
export default (state=defaultState,action)=>{
//input
if (action.type===CHANGE_INPUT_VALUE){
const newState=JSON.parse(JSON.stringify(state));
//简单的深拷贝
newState.inputValue=action.value;
return newState;
}
//button
if (action.type===ADD_TODO_ITEM){
//把老数据拷贝一份
const newState=JSON.parse(JSON.stringify(state));
//在列表中新加输入框内容
newState.list.push(newState.inputValue);
//点击提交之后,输入框清空
newState.inputValue='';
// console.log(newState);
//返回给store
return newState;
}
//点击删除
if (action.type===DELETE_TODO_ITEM){
const newState=JSON.parse(JSON.stringify(state));
newState.list.splice(action.index,1);
return newState;
}
return state;
}