React简单案例
# React简单案例
# TodoList案例
# 展示效果
拆分组件:将整个应用拆成多个组件
- Header:页眉输入框
- List:列表展示
- Item:列表中的每一项
- Footer:页脚部分
# 安装库
npm i nanoid # 用于产生唯一id
npm i prop-types # 用于限制props类型、必要性
1
2
2
# 相关知识点
- 拆分组件、实现静态组件,注意:className、style 的写法
- 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
- 某个组件使用:放在自身的state中
- 某些组件使用:放在他们共同的父组件 state 中(官方称此操作为:状体提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父组件提前给子组件传递一个函数
- 注意 defaultChecked 和 checked的区别,类似的还有:defaultvalue 和 value
- 状态在哪里,操作状态的方法就在哪里
# 代码
css样式代码省略了
src/App.jsx
import React,{Component} from 'react'
import Header from './components/Header'
import Footer from './components/Footer'
import List from './components/List'
import './App.css'
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:false},
]}
// 用于添加todo,接受参数是todo对象
addTodo = (todoObj) => {
const {todos} = this.state
const newTodos = [todoObj,...todos]
this.setState({todos:newTodos})
}
// 用于更新一个TODO对象
updateTodo = (id, done) => {
const {todos} = this.state
// 匹配处理数据
const newTodos = todos.map((todoObj)=>{
if(todoObj.id === id) return {...todoObj,done}
else return todoObj
})
this.setState({todos:newTodos})
}
// 用于删除一个todo对象
deleteTodo = (id) => {
const {todos} = this.state
const newTodos = todos.filter((todoObj) => {
return todoObj.id !== id
})
this.setState({todos:newTodos})
}
// 全选todo对象
checkAllTodo = (done) => {
const {todos} = this.state
const newTodos = todos.map((todoObj)=>{
return {...todoObj, done:done}
})
this.setState({todos:newTodos})
}
// 清除所有已完成
clearAllDone = () => {
const {todos} = this.state
const newTodos = todos.filter((todoObj)=>{
return !todoObj.done
})
this.setState({todos:newTodos})
}
render(){
return (
<div className='todo-container'>
<Header addTodo={this.addTodo}/>
<List todos={this.state.todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
<Footer todos={this.state.todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/>
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
src/components/Footer/index.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './index.css'
export default class Footer extends Component {
// 对接收的props进行类型、必要性的限制
static propTypes = {
addTodo:PropTypes.func.isRequired
}
handleCheckAll = (event) => {
this.props.checkAllTodo(event.target.checked)
}
handleClearAllDone = () => {
this.props.clearAllDone()
}
render() {
const {todos} = this.props
const doneCount = todos.reduce((pre, cur) => {
return pre + (cur.done ? 1 : 0)
}, 0)
const total = todos.length
return (
<div className='todo-footer'>
<label>
<input type='checkbox' onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false} />
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/src/components/Header/index.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {nanoid} from 'nanoid'
import './index.css'
export default class Header extends Component {
// 对接收的props进行类型、必要性的限制
static propTypes = {
addTodo:PropTypes.func.isRequired
}
handleKeyUp = (event) => {
// 结构赋值,回车是13
const {keyCode, target} = event
// 判断是否为回车按键
if (keyCode !== 13) return
// 添加的todo名字不能为空
if (target.value.trim() === ''){
alert('输入不能为空')
target.value = ''
return
}
// 准备好todo对象
// npm install nanoid,通过nanoid生成唯一的id
const todoObj = {id:nanoid(),name:target.value,done:false}
// 子组件给父组件,通过调用父组件的函数
this.props.addTodo(todoObj)
// 清空输入
target.value = ''
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder='请输入你的任务名称,按回车键确认' />
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/src/components/Item
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './index.css'
export default class Item extends Component {
// 对接收的props进行类型、必要性的限制
static propTypes = {
updateTodo:PropTypes.func.isRequired,
deleteTodo:PropTypes.func.isRequired
}
state = {mouse:false}
// 鼠标移入移除回调
handleMouse = (flag) => {
return () => {
this.setState({mouse:flag})
}
}
// 勾选或取消勾选
handleCheck = (id) => {
return (event)=> {
this.props.updateTodo(id, event.target.checked)
}
}
// 删除一个tod (不用高阶函数实现)
handleDelete = (id) => {
if (window.confirm('确认删除吗?')){
this.props.deleteTodo(id)
}
}
render() {
const {id,name,done} = this.props
const {mouse} = this.state
return (
<li style={{background:mouse ? "#ddd" : "white"}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
<label>
<input type='checkbox' checked={done} onChange={this.handleCheck(id)}/>
<span>{name}</span>
</label>
<button onClick={() => {this.handleDelete(id)}} className='btn btn-danger' style={{display:mouse?'block':'none'}}>删除</button>
</li>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/src/components/List
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
export default class List extends Component {
// 对接收的props进行类型、必要性的限制
static propTypes = {
todos:PropTypes.array.isRequired,
updateTodo:PropTypes.func.isRequired,
deleteTodo:PropTypes.func.isRequired
}
render() {
const {todos} = this.props
return (
<ul className="todo-main">
{
todos.map((todo) => {
return <Item key={todo.id} {...todo} updateTodo={this.props.updateTodo} deleteTodo={this.props.deleteTodo}/>
})
}
</ul>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Github搜索用户案例
# 展示效果
输入用户点击搜索后,即可看到用户列表,点击用户头像可跳转到用户Github主页。
# 代码Axios
数据的传输方式:父传给子
设置代理:/src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function(app){
app.use(
createProxyMiddleware('/api1',{
target:'https://api.github.com',
changeOrigin:true,
pathRewrite:{'^/api1':''}
})
)
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
APP:/src/App.jsx
//创建外壳组件APP
import React,{Component} from 'react'
import Header from './components/header/header';
import List from './components/list/list'
export default class App extends Component{
state = {
Git:[],
isFrist:true,
isLoad:false,
isError:''
}
updateAppState = (stateObj) =>{
this.setState(stateObj)
}
render(){
//通过 ...将状态中的全部赋值过去
return (
<div className="container">
<Header updateAppState = {this.updateAppState} />
<List {...this.state} />
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
搜索框:/src/components/header/header.jsx
import React, { Component } from 'react'
import axios from 'axios';
export default class Header extends Component {
search = () =>{
//const {value} = this.KeyValue;
//连续解构赋值,拿到this下面的KeyValue中的value,并进行重命名为KeyWord
const {KeyValue:{value:keyWord}} = this;
//在搜索之前设置,搜索的开始,结束第一次展示
this.props.updateAppState({isFrist:false,isLoad:true})
//切记在配置代理了之后一定需要添加相应的路径
axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
success => {
this.props.updateAppState({Git:success.data.items,isLoad:false});
},
error => {
this.props.updateAppState({isError:error.message,isLoad:false});
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">搜索GitHub用户</h3>
<div>
{/*使用ref拿到输入的数据,ref中使用回调函数的形式,在实例对象中创建一个KeyValue的属性,值是该节点*/}
<input ref={ c => this.KeyValue = c} type="text" placeholder="输入关键词进行搜索"/>
<button onClick = {this.search}>搜索</button>
</div>
</section>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
展示列表:/src/components/list/list.jsx
import React, { Component } from 'react';
import './list.css'
export default class List extends Component {
render() {
const {Git,isFrist,isLoad,isError} = this.props;
return (
<div className="row">
{
//因为不能在JSX语法中使用if,只能是表达式,所以可以是有用三元运算符进行判断。
isFrist ? <h1>欢迎进入页面</h1> :
isLoad ? <h2>正在搜索页面</h2> :
isError !== ''? <h1>{isError}</h1> :
//注意传递过来的一定是一个数组,不能是一个对象
//要不然初始化的时候对象就是 undefined 在遍历的时候就会出错
Git.map((git)=>{
return (
<div className="card" key = {git.id}>
<a href={git.html_url} target="_blank" rel="noreferrer">
<img alt="headImg" src={git.avatar_url} style={{width:'100px'}}/>
</a>
<p className="card-text">{git.login}</p>
</div>
)
})
}
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 代码Pubsub
工具库:PubSubJS
下载:npm install pubsub-js --save
Github:https://github.com/mroderick/PubSubJS
/src/App.jsx
import React,{Component} from 'react'
import Header from './components/header/header';
import List from './components/list/list'
export default class App extends Component{
render(){
//通过 ...将状态中的全部赋值过去
return (
<div className="container">
<Header />
<List />
</div>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/src/components/header/header.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js';
import axios from 'axios';
export default class Header extends Component {
search = () =>{
//连续解构赋值,拿到this下面的KeyValue中的value,并进行重命名为KeyWord
const {KeyValue:{value:keyWord}} = this;
//在搜索之前设置,搜索的开始,结束第一次展示
PubSub.publish('search', {isFrist:false,isLoad:true})
//切记在配置代理了之后一定需要添加相应的路径
axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
success => {
PubSub.publish('search', {Git:success.data.items,isLoad:false})
},
error => {
PubSub.publish('search', {isError:error.message,isLoad:false})
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">搜索GitHub用户</h3>
<div>
{/*使用ref拿到输入的数据,ref中使用回调函数的形式,在实例对象中创建一个KeyValue的属性,值是该节点*/}
<input ref={ c => this.KeyValue = c} type="text" placeholder="输入关键词进行搜索"/>
<button onClick = {this.search}>搜索</button>
</div>
</section>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/src/components/list/list.jsx
import React, { Component } from 'react';
import PubSub from 'pubsub-js';
import './list.css'
export default class List extends Component {
state = {
Git:[],
isFrist:true,
isLoad:false,
isError:''
}
componentDidMount() {
this.token = PubSub.subscribe('search', (_,stateObj)=>{
this.setState(stateObj)
})
}
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
render() {
const {Git,isFrist,isLoad,isError} = this.state;
return (
<div className="row">
{
//因为不能在JSX语法中使用if,只能是表达式,所以可以是有用三元运算符进行判断。
isFrist ? <h1>欢迎进入页面</h1> :
isLoad ? <h2>正在搜索页面</h2> :
isError !== ''? <h1>{isError}</h1> :
//注意传递过来的一定是一个数组,不能是一个对象
//要不然初始化的时候对象就是 undefined 在遍历的时候就会出错
Git.map((git)=>{
return (
<div className="card" key = {git.id}>
<a href={git.html_url} target="_blank" rel="noreferrer">
<img alt="headImg" src={git.avatar_url} style={{width:'100px'}}/>
</a>
<p className="card-text">{git.login}</p>
</div>
)
})
}
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
编辑 (opens new window)
上次更新: 2023/08/24, 15:52:58