真正的挑战要开始了~~~
这里我采用自顶向下的叙述方法,来描述组件的结构,大家可以先按照顺序依次阅读,最后再回头完整的看一遍,就能明白其中的设计方法啦~~
-
App.jsx
在根组件中,我们使用 TodoListContext.Consumer
将从模拟接口获取的数据,传递给数据消费者,即 TodoList
组件。
在未获取到数据前,使用 Loading
组件来渲染当前区域。
import "./App.css";
import Loading from "./components/Loading";
import TodoList from "./components/TodoList";
import { Http, TodoListContext } from "./utils";
function App() {
return (
<div className="App">
<Http.Get url="xxx" loading={<Loading />}>
<TodoListContext.Consumer>
{(value) => <TodoList listData={value} />}
</TodoListContext.Consumer>
</Http.Get>
</div>
);
}
export default App;
-
TodoList
import React from "react";
import "./index.css";
import TodoItem from "../TodoItem";
import InputItem from "../InputItem";
import TodoTabs from "../TodoTabs";
export default class TodoList extends React.Component {
state = {
filterType: "",
listData: [],
originData: [],
};
// 存储 listData
async componentDidMount() {
this.setState({
listData: this.props.listData,
originData: this.props.listData,
});
}
// 点击 input: checkbox ,把当前任务置为已完成
complate(name) {
this.setState({
listData: [
...this.state.listData.map((item) => {
if (item.name === name) {
item.isChecked = !item.isChecked;
}
return item;
}),
],
});
}
// 删除列表项
deleteItem(name) {
this.setState({
listData: [...this.state.listData.filter((item) => item.name !== name)],
});
}
// 增加列表项
addItem(name) {
if (name.trim()) {
this.setState({
listData: [
...this.state.listData,
{
name,
isDelete: false,
isChecked: false,
},
],
});
}
}
// 设置当前过滤的类型
filterListData(filter) {
this.setState({
filterType: filter,
});
}
render() {
const listData = this.state.listData.filter(
(item) => !!item[this.state.filterType || "name"]
);
return (
<div className="todo_wrapper">
<InputItem
listData={this.state.listData}
addItem={this.addItem.bind(this)}
/>
<TodoTabs
filterType={this.state.filterType}
filterListData={this.filterListData.bind(this)}
/>
<ul>
{listData.length ? (
listData.map((item, index) => (
<TodoItem
key={index}
data={item}
complate={this.complate.bind(this, item.name)}
deleteItem={this.deleteItem.bind(this, item.name)}
/>
))
) : (
<div>没有数据</div>
)}
</ul>
</div>
);
}
}
.todo_wrapper{
width: 500px;
margin: auto;
padding: 50px 0;
ul{
padding: 0;
}
}
-
InputItem
React.createRef
创建引用,来获取输入框的 value
import React from "react";
import "./index.scss";
export default class InputItem extends React.Component {
constructor(props) {
super(props);
this.InputRef = React.createRef();
}
state = {
addItem: "",
};
// 增加列表项
addItem() {
this.props.addItem(this.InputRef.current.value);
this.InputRef.current.value = "";
}
render() {
return (
<div className="input_wrapper">
<input type="text" placeholder="请输入待办事项" ref={this.InputRef} />
<button onClick={() => this.addItem()}>增加</button>
</div>
);
}
}
.input_wrapper {
display: flex;
align-items: center;
height: 30px;
input {
flex: 1;
height: 100%;
margin-right: 10px;
outline: none;
padding-left: 1em;
border: 1px solid #ccc;
}
button {
width: 100px;
height: 100%;
border: none;
cursor: pointer;
transition: all .3s;
&:hover {
background-color: black;
color: #fff;
}
}
}
-
TodoTabs
这个组件可以切换要展示的不同列表项:全部/已完成
当点击某个类型时,通过 props
调用父组件的 filterListData
方法过滤数据
import React from "react";
import "./index.scss";
export default class TodoTabs extends React.Component {
state = {
selIdx: 0,
tabList: [
{
name: "全部",
filter: "",
},
{
name: "已完成",
filter: "isChecked",
},
],
};
// 选择某个数据类型
setSelIdx(idx) {
this.setState({
selIdx: idx,
});
const { selIdx, tabList } = this.state;
// 调用父组件的方法,并将类型通过参数传给父组件
this.props.filterListData(tabList[selIdx].filter);
}
render() {
return (
<div className="tabs">
{this.state.tabList.map((item, index) => (
<div
key={index}
className={`tab ${this.props.filterType === item.filter ? "sel" : ""}`}
onClick={()=>this.props.filterListData(item.filter)}
>
{item.name}
</div>
))}
</div>
);
}
}
.tabs {
display: flex;
.tab {
width: 80px;
margin: 20px 10px 10px 0;
padding: 5px 10px;
border: 1px solid #ccc;
box-sizing: border-box;
cursor: pointer;
transition: all .3s;
&.sel,
&:hover {
background-color: #000;
color: #fff;
}
}
}
-
TodoItem
待办列表项
import React from "react";
import IsChecked from "../IsCheck";
import "./index.scss";
export default class TodoItem extends React.Component {
render() {
const { name, isChecked, isDelete } = this.props.data;
return (
<div className="todo_item">
<div className="left">
<IsChecked isChecked={isChecked} complate={this.props.complate} />
<span className={isChecked ? "del" : ""}>{name}</span>
</div>
<button onClick={this.props.deleteItem}>删除</button>
</div>
);
}
}
.todo_item {
display: flex;
align-items: center;
justify-content: space-between;
height: 30px;
margin-bottom: 10px;
.left {
flex: 1;
display: flex;
align-items: center;
margin-right: 20px;
text-align: left;
span.del {
flex: 1;
text-decoration: line-through;
}
}
button {
width: 100px;
height: 100%;
border: none;
cursor: pointer;
transition: all .3s;
&:hover {
background-color: black;
color: #fff;
}
}
}
-
IsChecked
操作待办事项
import React from 'react';
import './index.scss'
export default class IsChecked extends React.Component{
render(){
return (
<input type="checkbox" checked={this.props.isChecked} onChange={this.props.complate} />
)
}
}
input[type="checkbox"]{
width: 20px;
height: 20px;
border-radius: 50%;
}
原文始发于微信公众号(前端华先生):React实践项目–todolist(3)基本组件的编写与联调
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/55323.html