需求:TodoList组件化实现此功能
- 显示所有todo列表
- 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
完成TodoList组件的静态页面以及拆解组件
动态初始化列表
//App.jsx
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
render() {
const { todos } = this.state
return (
)
}
}
//List.jsx
export default class List extends Component {
render() {
// 接收来自App父组件传递的数据
const { todos } = this.props
return (
{todos.map(todo => {
return
})}
)
}
}
//Item.jsx
export default class Item extends Component {
render() {
// 接收来自List父组件传递的数据
const { id, name, done } = this.props
return (
-
)
}
}
完成添加“任务”的功能(父子组件之间传值)
在父组件中创建一个添加数据的方法
addTodo
,然后将方法传递给子组件,当子组件向该方法传递了参数后,父组件便可以接收到子组件传递的参数
//App.jsx
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
//addTodo用于添加一个todo,接收的参数是todo对象
addTodo = (todoObj) => {
console.log('APP:' + data);
// 获取原数组
const { todos } = this.state
// 追加一个新数组
const newTodos = [todoObj, ...todos]
// 更新状态
this.setState({ todos: newTodos })
}
render() {
const { todos } = this.state
return (
)
}
}
子组件
Header
创建一个点击回车键回调的方法handlerKeyUp
,在该方法中向从父组件接收到的方法addTodo
中传递参数,将新添加的数据反馈给父组件
//Header.jsx
export default class Header extends Component {
// 键盘事件的回调
handlerKeyUp = (event) => {
// keyCode 触发事件的按键码
// 解构赋值keyCode, target
const { keyCode, target } = event
// 判断是否是回车按键
if (keyCode !== 13) return
// 添加的todo名字不能为空
if (target.value.trim() == '') {
alert('输入不能为空')
return
}
// 准备一个新的todo对象
const todoObj = {
// 使用nanoid库创建一个不重复的ID
id: nanoid(),
name: target.value,
done: false
}
this.props.addTodo(todoObj);
// 清空输入
target.value='';
}
render() {
return (
)
}
}
完成删除"任务"的功能(祖孙组件之间的传值)
在祖组件中创建一个删除数据的方法
deleteTodo
,然后将方法传递给子组件,再由子组件传递给孙组件,当孙组件向该方法传递了参数后,祖组件便可以接收到子组件传递的参数
//App.jsx
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
// deleteTodo用于删除一个todo对象
deleteTodo = (id) => {
const { todos } = this.state
// 删除指定id的todo对象
const newTodos = todos.filter(todoObj => {
return todoObj.id !== id
})
// 更新状态
this.setState({ todos: newTodos })
}
render() {
const { todos } = this.state
return (
)
}
}
List
组件接收从父组件传递来的删除数据的方法deleteTodo
,再将deleteTodo
方法传递给自己的子组件Item
//List.jsx
export default class List extends Component {
render() {
const { todos, updateTodo} = this.props
return (
{todos.map(todo => {
return
})}
)
}
}
Item
组件创建一个删除“任务”的回调方法handleDelete
,然后在该方法中调用deleteTodof
方法,将需要删除的数据id
传递给祖组件App
//Item.jsx
export default class Item extends Component {
// 删除一个todo的回调
handleDelete = (id) => {
// console.log('通知App删除'+id);
if (window.confirm('确定删除吗?')) {
this.props.deleteTodo(id)
}
}
render() {
const { id, name, done } = this.props
const { mouse } = this.state
return (
- { backgroundColor: mouse ? '#ddd' : 'white' }} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
)
}
}
为每个Item
组件添加鼠标移入高亮且删除按钮显现、鼠标移出无高亮显示且删除按钮隐藏的效果
//Item.jsx
export default class Item extends Component {
state = {
mouse: false //标识鼠标移入、移出
}
//鼠标移入、移出的回调
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
}
}
// 删除一个todo的回调
handleDelete = (id) => {
if (window.confirm('确定删除吗?')) {
this.props.deleteTodo(id)
}
}
render() {
const { id, name, done } = this.props
const { mouse } = this.state
return (
- { backgroundColor: mouse ? '#ddd' : 'white' }} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
)
}
}
修改Item
组件勾选框的状态和修改数据(祖孙组件之间的传值)
在祖组件中创建一个更新数据的方法
updateTodo
,然后将方法传递给子组件,再由子组件传递给孙组件,当孙组件向该方法传递了参数后,祖组件便可以接收到子组件传递的参数
//App.jsx
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
// updateTodo用于更新一个todo对象
updateTodo = (id, done) => {
// 获取状态中的todos
const { todos } = this.state
// 匹配处理数据
const newTodos = todos.map(todoObj => {
if (todoObj.id === id) return { ...todoObj, done }
else return todoObj
})
this.setState({ todos: newTodos })
}
render() {
const { todos } = this.state
return (
)
}
}
List
组件接收从父组件传递来的删除数据的方法updateTodo
,再将updateTodo
方法传递给自己的子组件Item
//List.jsx
export default class List extends Component {
render() {
const { todos, updateTodo, deleteTodo } = this.props
return (
{todos.map(todo => {
return
})}
)
}
}
Item
组件创建一个更新数据的回调方法handleDelete
,然后在该方法中调用updateTodo
方法,将需要更新的数据id
传递给祖组件App
export default class Item extends Component {
state = {
mouse: false //标识鼠标移入、移出
}
//鼠标移入、移出的回调
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
// console.log(flag);
}
}
//勾选、取消勾选某一个todo的回调
handleChange = (id) => {
return (event) => {
// console.log(id,event.target.checked);
this.props.updateTodo(id, event.target.checked);
}
}
// 删除一个todo的回调
handleDelete = (id) => {
// console.log('通知App删除'+id);
if (window.confirm('确定删除吗?')) {
this.props.deleteTodo(id)
}
}
render() {
const { id, name, done } = this.props
const { mouse } = this.state
return (
- { backgroundColor: mouse ? '#ddd' : 'white' }} onMouseLeave={this.handleMouse(false)} onMouseEnter={this.handleMouse(true)}>
)
}
}
完成底部组件全选修改数据的功能
//APP.jsx
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
// checkAllTodo用于全选
checkAllTodo=(done)=>{
// 获取原来的todos
const {todos}=this.state
// 处理数据
const newTodos=todos.map(todoObj=>{
return {...todoObj,done}
})
// 更新数据
this.setState({todos:newTodos})
}
render() {
const { todos } = this.state
return (
)
}
}
//Footer.jsx
export default class Footer extends Component {
handleChange=(event)=>{
this.props.checkAllTodo(event.target.checked)
}
render() {
const {todos}=this.props
// 已完成的个数
const doneCount=todos.reduce((pre,todo)=>pre+(todo.done?1:0),0)
// 总数
const total=todos.length
return (
已完成{doneCount} / 全部{total}
)
}
}
完成“清楚已完成任务”按钮的功能
export default class App extends Component {
// 初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '敲代码', done: false },
{ id: '004', name: '逛街', done: true },
]
}
// clearAllDone用于清除所有已完成的
clearAllDone=()=>{
// 获取原来的todos
const {todos}=this.state
// 处理数据
const newTodos= todos.filter(todoObj=>{
return !todoObj.done
})
// 更新状态
this.setState({todos:newTodos})
}
render() {
const { todos } = this.state
return (
)
}
}
export default class Footer extends Component {
handleClearAllDone=()=>{
this.props.clearAllDone()
}
render() {
const {todos}=this.props
// 已完成的个数
const doneCount=todos.reduce((pre,todo)=>pre+(todo.done?1:0),0)
// 总数
const total=todos.length
return (
已完成{doneCount} / 全部{total}
)
}
}
todoList案例相关知识点:
效果:
搭建组件静态效果
//List.jsx
export default class List extends Component {
render() {
return (
)
}
}
//Search.jsx
export default class Search extends Component {
render() {
return (
搜索Github用户
)
}
}
配置脚手架代理(配置代理)
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
createProxyMiddleware('/api1', {
target: 'http://localhost:5000',
secure: false, //
changeOrigin: true,
pathRewrite: {
'^/api1': '',
},
})
)
}
完成搜索框的搜索和发送请求的功能
收集Search
组件输入框的内容
export default class Search extends Component {
Search = () => {
// 连续解构赋值+重命名
const { KeyWordElement: { value: keyWord } } = this;
console.log(keyWord);
}
render() {
return (
搜索Github用户
this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" />
)
}
}
发送请求
export default class Search extends Component {
Search = () => {
const { KeyWordElement: { value: keyWord } } = this;
// 发送请求
axios.get(`/api1/search/users2?q=${keyWord}`).then(
response => {
console.log(response.data.items);
},
error => {
console.log('失败了', error);
}
)
}
render() {
return (
搜索Github用户
this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" />
)
}
}
动态初始化组件的状态
//App.jsx
export default class App extends Component {
// 初始化状态
state={
users:[],//users初始值为数组
isFirst:true,//是否为第一次打开页面
isLoading:false,//标识是否处于加载中
err:""// 存储请求相关的错误信息
}
render() {
const {users}=this.state
return (
)
}
}
//List.jsx
export default class List extends Component {
render() {
const { users,isFirst,isLoading,err } = this.props
return (
{
//使用三元运算符动态显示组件的内容
isFirst?欢迎使用,输入关键字,随后点击搜索
:
isLoading?Loading......
:
err?{color:'red'}}>{err}
:
users.map(user => {
return (
{ width: '100px' }} />
{user.login}
)
})
}
)
}
}
发起请求并将数据显示到组件上
//App.jsx
export default class App extends Component {
// 初始化状态
state={
users:[],//users初始值为数组
isFirst:true,//是否为第一次打开页面
isLoading:false,//标识是否处于加载中
err:""// 存储请求相关的错误信息
}
// 更新App的state
updateAppState=(stateObj)=>{
this.setState(stateObj)
}
render() {
const {users}=this.state
return (
)
}
}
//Search.jsx
export default class Search extends Component {
Search = () => {
// 连续解构赋值+重命名
const { KeyWordElement: { value: keyWord } } = this;
// 发送请求前通知App更新状态
this.props.updateAppState({ isFirst:false,isLoading:true,})
// 发送请求
axios.get(`/api1/search/users2?q=${keyWord}`).then(
response => {
//请求成功后通知App更新状态
this.props.updateAppState({isLoading:false,users:response.data.items})
},
error => {
console.log('失败了', error);
// 请求成功后通知App更新状态
this.props.updateAppState({isLoading:false,err:error.message})
}
)
}
render() {
return (
搜索Github用户
this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" />
)
}
}
消息订阅-发布机制
工具库: PubSubJS
下载:
npm install pubsub-js --save
使用:
import PubSub from 'pubsub-js' //引入
PubSub.subscribe('delete', function(data){ }); //订阅
PubSub.publish('delete', data) //发布消息
PubSub.unsubscribe(this.xxx)
初始化各个组件
//App.jsx
export default class App extends Component {
render() {
return (
)
}
}
//Search.jsx
export default class Search extends Component {
render() {
return (
搜索Github用户
)
}
}
//List.jsx
export default class List extends Component {
render() {
return (
)
}
}
初始化组件的状态
//List.jsx
export default class List extends Component {
// 初始化状态
state = {
users: [],
isFirst: true,
isLoading: false,
err: ""
}
render() {
const { users, isFirst, isLoading, err } = this.state
return (
{
isFirst ? 欢迎使用,输入关键字,随后点击搜索
:
isLoading ? Loading......
:
err ? { color: 'red' }}>{err}
:
users.map(user => {
return (
{ width: '100px' }} />
{user.login}
)
})
}
)
}
}
发布消息与发送请求数据
//Search.jsx
export default class Search extends Component {
Search = () => {
const { KeyWordElement: { value: keyWord } } = this;
PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
axios.get(`/api1/search/users2?q=${keyWord}`).then(
response => {
PubSub.publish('Spongebob',{isLoading:false,users:response.data.items})
},
error => {
PubSub.publish('Spongebob',{isLoading:false,err:error.message})
}
)
}
render() {
return (
搜索Github用户
this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" />
)
}
}
订阅消息与动态更新数据
export default class List extends Component {
// 初始化状态
state = {
users: [],
isFirst: true,
isLoading: false,
err: ""
}
componentDidMount() {
this.token = PubSub.subscribe('Spongebob', (_, stateObj) => {
this.setState(stateObj)
})
}
componentWillUnmount() {
// 组件销毁后取消订阅消息
PubSub.unsubscribe(this.token)
}
render() {
const { users, isFirst, isLoading, err } = this.state
return (
{
isFirst ? 欢迎使用,输入关键字,随后点击搜索
:
isLoading ? Loading......
:
err ? { color: 'red' }}>{err}
:
users.map(user => {
return (
{ width: '100px' }} />
{user.login}
)
})
}
)
}
}
Fetch
特点:
- fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
- 老版本浏览器可能不支持
export default class Search extends Component {
Search = async() => {
const { KeyWordElement: { value: keyWord } } = this;
PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
// 发送请求
try {
const response=await fetch(`/api1/search/users2?q=${keyWord}`);
PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
const result=await response.json();
// PubSub.publish('Spongebob',{isLoading:false,users:result})
console.log(result);
} catch (error) {
console.log('请求出错',error);
}
}
render() {
return (
搜索Github用户
this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" />
)
}
}
github搜索案例相关知识点:
设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
ES6小知识点:解构赋值+重命名
let obj {a:{b:1}}
//传统解构赋值
const{a}=obj;
//连续解构赋值
const{a:(b}}=obj;
//连续解构赋值+重命名
const{a:{b:value}}=obj;
消息订阅与发布机制
componentwillUnmount
中取消订阅fetch发送请求(关注分离的设计思想)
try{
const response=await fetch(/api1/search/users2?q=${keyWord))
const data await response.json()
console.log(data);
}catch (error){
console.1og('请求出错',error);
}