React
基础知识
1.什么是 React ?
一个将数据渲染为 HTML 视图的开源 JavaScript 库。
2.入门案例
<!DOCTYPE html>
<html>
<head>
<title>hello react</title>
</head>
<body>
<div id="test"></div>
<!-- react 核心库,要最先引入-->
<script type="text/javascript" src="../js/react.development.js"></script>
<!--用于支持 react 操作dom 的周边库-->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!--用于将 jsx 转为 js-->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!--代表里面写的内容为 jsx,并将内容翻译为 babel-->
<script type="text/babel">
// 创建虚拟 DOM
const VDOM = <h1>Hello React</h1>;
// 渲染虚拟 DOM 到页面, render(虚拟DOM,容器)
ReactDOM.render(VDOM, document.getElementById('test'));
</script>
</body>
</html>
3.创建虚拟 DOM 的两种方式 【div > span > h1】
// 采用 jsx 的方式
const DOM1 = <h1 id='title'><span>Hello,React</span></h1>;
// 采用 js 的方式 >> 如果存在多层嵌套关系就会非常繁琐
const DOM2 = React.createElement('h1', {id="title"}, React.createElement('span', {}, 'Hello,React'));
4.虚拟DOM与真实DOM有什么区别?
- 虚拟 DOM 是一个普通的 Object 对象
- 虚拟 DOM 的属性比真实 DOM 的属性少
- 虚拟 DOM 最后会转为真实的 DOM 呈现在页面上
5.jsx 语法规则
-
定义虚拟 DOM 的时候无能添加引号
-
标签中混入 JS 表达式时要用 {} 来取值
-
样式的类名不要用 class,而是使用 className 代替
-
内联样式要写成 style={{key:value}} 的形式
-
虚拟 DOM 只能有一个根标签
-
标签必须闭合
-
标签的首字母为小写,转化的时候就会去对应 html 中同名标签;如果首字母为大写,那么就会去渲染自定义组件
6.练习 JSX 语法
- 要求动态展示如下效果:
<script type="text/babel">
const data = ['Angular', 'React', 'Vue'];
const VDOM = (
<div>
<h1>前端JS框架列表</h1>
<ul>
{
data.map((item, index) => {
return <li key={index}>{item}</li>;
})
}
</ul>
</div>
);
ReactDOM.render(VDOM, document.querySelector('#test'));
</script>
对于在 jsx 中想插入 js 表达式可以使用花括号,前提是我们要知道什么是表达式,什么是语句?
- 例如:a、a + b、demo(1)、arr.map()、function test() 【只要可以被变量接收的都是表达式】
- 对于 if、for、swith 等都是语句,也就是说不能写在 jsx 的 {} 中
7.如何创建一个函数式组件?
<script type="test/babel">
// 1.创建函数式组件
function MyComponent(){
return <h1>我是函数定义的组件,适用于简单组件</h1>
}
// 2.渲染组件到页面中
ReactDOM.render(<MyComponent />, document.getElementById('test'));
</script>
这部分的工作内容有两个:
- React 解析组件标签,找到了 MyComponent 组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
8.复习一下类的使用:
- 使用 constructor 关键字声明类的构造器,没有需求可以不创建构造器,当需要添加特定的属性就需要创建构造器
- 如果两个类存在继承关系,子类想在构造器中添加字段,首行必须调用 super 使用父类的构造器
- 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
9.如何创建一个类组件?
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
// 1.定义类式组件
class MyComponent extends React.Component{
render(){
return <h2>我是一个类式组件,可以用于实现一些复杂的组件</h2>;
}
}
// 2.将组件渲染到页面中
ReactDOM.render(<MyComponent />, document.getElementById('test'));
</script>
对它的流程也描述一下:
- React 解析组件标签,找到了 MyComponent 组件
- 发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法
- 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。
10.如何借助构造器初始化状态,并读取出状态?
<script type="text/babel">
// 1.创建组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot : false}
}
render(){
const {isHot} = this.state
return <h1>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'));
</script>
11.如何在 React 中如何绑定点击事件?
<script type="text/babel">
function demo(){
alert('点击绑定事件');
}
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot : false}
}
render(){
const {isHot} = this.state
// onclik 在 React 中为 onClick
return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
}
}
ReactDOM.render(<Weather />, document.getElementById('test'));
</script>
12.类中函数回调的时候,不是类的实例调的,属于直接调的该方法,this 为 undefine
1.回顾上集,那么应该如何通过 state 来实现点击切换呢?
<!DOCTYPE html>
<html>
<head>
<title>底板</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
function demo(){
alert('点击绑定事件');
}
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot : false}
// 作用就是将这个方法绑定到 Wether 的实例上, bind 方法会返回一个新函数
this.changeWeather = this.changeWeather.bind(this)
}
render(){
const {isHot} = this.state
// onclik 在 React 中为 onClick
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
}
changeWeather(){
const isHot = this.state.isHot;
// 状态不能直接修改
// this.state.isHot = !isHot;
this.setState({isHot:!isHot});
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
</body>
</html>
下面是代码的简化版本:
<!DOCTYPE html>
<html>
<head>
<title>底板</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Weather extends React.Component{
state = {isHot : false};
changeWeather = ()=>{
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
render(){
const {isHot} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
</body>
</html>
2.组件第二大核心属性 props,演示一下如何通过 props 外部传递参数值?
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render(){
const {name, sex, age} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件
ReactDOM.render(<Person name="张三" sex="男" age="21"/>, document.querySelector('#test'));
</script>
此处传递参数可以用展开运算符
const args = {name="张三" sex="男" age="21"};
ReactDOM.render(<Person {...args}/>, document.querySelector('#test'));
展开运算符正常是不可以展开对象的,一般用于数组中比较多(复制、连接),因为 babel + react 实现了展开对象
// 复制数组
const arr1 = [1, 2, 3];
// const arr2 = arr1; 属于引用传递
const arr2 = {...arr1};
// 连接数组
const arr3 = {...arr1, ....arr2};
// 在函数中使用
function sun(...numbers){
return numbers.reduce((preNumber, curNumber) =>{
return preNumber + curNumber;
})
}
// 构架字面量
let person = {name:'tom', age : 18};
let person2 = {...person}
let person3 = {...person, name:"jack", address:"中国"}
3.对 props 中数据类型限制,参数必要性限制
// 在 React 16.X 版本,开始将 props 属性限制的依赖分出一个单独的依赖包
<script type="text/javascript" src="../js/prop-types.js"></script>
// 定义规则和默认值,下面这两段写在 script 标签里
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: propTypes.func,
}
Person.defaultProp = {
sex: "不男不女",
age: 18,
}
// 简化版本
<!DOCTYPE html>
<html>
<head>
<title>props 的使用</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 创建组件
class Person extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: propTypes.func,
}
static defaultProps = {
sex: "不男不女",
age: 18,
}
render(){
const {name, sex, age} = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件
ReactDOM.render(<Person name="张三" sex="男" age="21"/>, document.querySelector('#test'));
</script>
</body>
</html>
4.类组件和函数式组件对于 props 的使用:
(1)类组件的构造器中可以使用 props,但一定要调用 super 传递 props
class Person extends React.Component {
constructor(props){
super(props);
}
static propTypes = {
}
static defaultProps = {
}
}
(2)函数式组件可以使用 props,但是不能使用 state 和 refs
function Person(props){
const {name, age, sex} = props;
return (
<ul>
<li>{姓名: name}</li>
<li>{性别: sex}</li>
<li>{年龄: age}</li>
</ul>
);
}
Person.propTypes = {
}
Person.defaultProps = {
}
5.ref 的三种形式:
(1)字符串形式的 ref >> 已经不推荐使用,因为效率比较低
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<div id="test"></div>
<script type="text/babel">
class Demo extends React.Component {
showData = () => {
const { input1 } = this.refs
alert(input1.value)
}
// Shift + Alt + F 快速格式化代码
loseBlur = () => {
const { input2 } = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button ref="button1" onClick={this.showData}>点击提示左侧数据</button>
<input ref="input2" onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
</script>
</body>
</html>
(2)回调函数的 ref >> 在更新的时候可能会出现调用两次的现象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<div id="test"></div>
<script type="text/babel">
class Demo extends React.Component {
showData = () => {
const { input1 } = this
alert(input1.value)
}
// Shift + Alt + F 快速格式化代码
loseBlur = () => {
const { input2 } = this
alert(input2.value)
}
render() {
return (
<div>
<input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧数据</button>
<input ref={currentNode => this.input2 = currentNode} onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
</script>
</body>
</html>
可以通过将匿名函数赋值给类实例本身,然后再赋值给 ref
// 写在类组件中
saveInput = (c) => {
this.input1 = c;
}
// 写在 render 方法的 return 中的具体的一个标签中
<input ref=this.saveInput type="text" /> >
(3)通过 React 的 createRef API,但是注意是专人专用的,一次 createRef 创建的容器只能存储一个节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<div id="test"></div>
<script type="text/babel">
class Demo extends React.Component {
// 在类组件中创建容器
myRef = React.createRef()
myRef2 = React.createRef()
showData = () => {
alert(this.myRef.current.value)
}
// Shift + Alt + F 快速格式化代码
loseBlur = () => {
alert(this.myRef2.current.value)
}
render() {
return (
<div>
<input ref= {this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧数据</button>
<input ref= {this.myRef2} onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
</script>
</body>
</html>
1.react 收集表单数据
- 非受控组件就是数据现用现取
- 非受控组件就是提前将数据存好
(1)非受控组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Login extends React.Component {
getData = (event) => {
event.preventDefault()
const {username, password} = this
alert(`账号为 ${username.value}, 密码为${password.value}`)
}
render(){
return (
<form onSubmit={this.getData}>
账号:<input ref = {u => this.username = u} type="text" name="username"/>
密码:<input ref = {p => this.password = p} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
// 2.渲染组件
ReactDOM.render(<Login/>, document.getElementById('test'));
</script>
</body>
</html>
(2)受控组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Login extends React.Component {
// 初始状态
state = {
username : '',
password : ''
}
// 回调函数
getData = (event) => {
const {username, password} = this.state
alert(`账号为 ${username}, 密码为${password}`)
event.preventDefault()
}
// 将表单中用户名存储到 state 中
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
// 将表单中密码存储到 state 中
savePassword = (event) => {
this.setState({ password: event.target.value })
}
// 返回 React 元素
render() {
return (
<form onSubmit={this.getData}>
账号:<input type="text" name="username" onChange={this.saveUsername}/>
密码:<input type="password" name="password" onChange={this.savePassword}/>
<button>登录</button>
</form>
)
}
}
// 2.渲染组件
ReactDOM.render(<Login />, document.getElementById('test'));
</script>
</body>
</html>
上面的代码存在一个问题,如果表单数据很多,就会出现大量重复的 saveXXX,那么如何解决呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Login extends React.Component {
// 初始状态
state = {
username : '',
password : ''
}
// 回调函数
getData = (event) => {
const {username, password} = this.state
alert(`账号为 ${username}, 密码为${password}`)
event.preventDefault()
}
// 保存数据到 state 中
saveFormData = (dataType) => {
return (event) => {
this.setState({[dataType] : event.target.value})
}
}
// 返回 React 元素
render() {
return (
<form onSubmit={this.getData}>
账号:<input type="text" name="username" onChange={this.saveFormData('username')}/>
密码:<input type="password" name="password" onChange={this.saveFormData('password')}/>
<button>登录</button>
</form>
)
}
}
// 2.渲染组件
ReactDOM.render(<Login />, document.getElementById('test'));
</script>
</body>
</html>
如果不用函数柯里化也是可以实现的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 1.创建组件
class Login extends React.Component {
// 初始状态
state = {
username : '',
password : ''
}
// 回调函数
getData = (event) => {
const {username, password} = this.state
alert(`账号为 ${username}, 密码为${password}`)
event.preventDefault()
}
// 保存数据到 state 中
saveFormData = (dataType, value) => {
this.setState({[dataType] : value})
}
// 返回 React 元素
render() {
return (
<form onSubmit={this.getData}>
账号:<input type="text" name="username" onChange={(e) => {this.saveFormData('username', e.target.value)}}/>
密码:<input type="password" name="password" onChange={(e) => {this.saveFormData('password', e.target.value)}}/>
<button>登录</button>
</form>
)
}
}
// 2.渲染组件
ReactDOM.render(<Login />, document.getElementById('test'));
</script>
</body>
</html>
2.引出 React 的声明周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_引出生命周期</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//创建组件
//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Life extends React.Component{
state = {opacity:1}
death = ()=>{
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//组件挂完毕
componentDidMount(){
console.log('componentDidMount');
this.timer = setInterval(() => {
//获取原状态
let {opacity} = this.state
//减小0.1
opacity -= 0.1
if(opacity <= 0) opacity = 1
//设置新的透明度
this.setState({opacity})
}, 200);
}
//组件将要卸载
componentWillUnmount(){
//清除定时器
clearInterval(this.timer)
}
//初始化渲染、状态更新之后
render(){
console.log('render');
return(
<div>
<h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Life/>,document.getElementById('test'))
</script>
</body>
</html>
3.React 生命周期
脚手架
1.使用脚手架快速生成一个最基础的模版?
// 1.全局安装手脚架库 create-react-app
npm i create-react-app -g
// 2.切换到待创建项目的目录,创建项目
create-react-app hello-react
// 3.进入到生成的项目文件夹
cd hello-react
// 4.启动项目
npm start
2.基于生成的模版写一个最简单的 Hello 应用
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React 脚手架</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Hello.js
import React, {Component} from 'react'
import './Hello.css'
export default class Hello extends Component {
render(){
return <h1 className='title'>Hello, React</h1>
}
}
Hello.css
.title {
background-color: aqua;
}
Welcome.js
import React, {Component} from 'react'
import './Welcome.css'
export default class Welcome extends Component {
render(){
return <h2 className='demo'>Welcome</h2>
}
}
Welcome.css
.demo {
background-color: chartreuse;
}
App.js
// 创建外壳组件
// 引入 React 核心库,因为 Component 和 React 采用了分别暴露才可以这样写,并不是解构赋值
import React, {Component} from 'react'
// 引入 Hello 组件
import Hello from './components/Hello/Hello.js'
import Welcome from './components/Welcome/Welcome.js'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Hello />
<Welcome />
</div>
);
}
}
// 采用默认暴露暴露组件
// export default App
index.js
// 引入 React 核心库
import React from 'react'
// 引入操作 dom 的组件
import ReactDOM from 'react-dom'
// 引入我们要渲染的组件
import App from './App'
// 渲染组件到 root 中
ReactDOM.render(<App/>, document.getElementById('root'));
3.如果多个组件里存在同名的样式,可能会出现冲突,所以介绍一下样式的模块化
在样式命名的时候中间加上 module,例如: index.module.css
在我们组件中引入样式就可以采用 import XXX from './index.module.css' 【原来 import './index.css'】
在使用 className 的时候,就要用 {} 取值,例如: className =
4.ToDo 案例
功能点介绍:
- 可以通过输入框添加任务到任务列表
- 当鼠标移动到具体的某个任务上任务条背景色变灰色,显示删除任务按钮,点击删除按钮会进行二次确
- 当所有任务被勾选,总复选框会被勾选,点击不勾选总复选框会清空所有任务复选框的勾选,根据勾选情况统计已完成和全部的任务
- 点击清除已完成的任务会删除所有勾选的任务
项目模块图如下:
代码内容如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React 脚手架</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Footer - index.css
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
Footer - index.jsx
import React, { Component } from 'react'
import './index.css'
export default class index extends Component {
// 全选复选框
handleCheckAll = (event) => {
this.props.checkAllTodo(event.target.checked)
}
// 清除所有已完成的 todo
handleClearAllDone = () => {
this.props.clearAllTodo()
}
render() {
const {todos} = this.props
const doneCount = todos.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
const total = todos.length
return (
<div className="todo-footer">
<label>
{/* 此处之所以不用 defaultChecked,是因为 defaultChecked 只有在第一次的时候生效,我们 checked 是以最后一次的为准 */}
<input type="checkbox" checked={doneCount === total && total !== 0 ? true : false} onChange={this.handleCheckAll}/>
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
</div>
)
}
}
Header- index.css
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
Header- index.jsx
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import PropTypes from 'prop-types'
import './index.css'
export default class index extends Component {
// 参数类型与必要性限制
static propTypes = {
addTodo: PropTypes.func.isRequired
}
// 输入框按键回调函数
handleKeyUp = (e) => {
const {keyCode, target} = e
if(keyCode !== 13) return
if(target.value.trim() === ''){
alert('输入不能为空')
return
}
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>
)
}
}
Item- index.css
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
Item- index.jsx
import React, { Component } from 'react'
import './index.css'
export default class index extends Component {
// 鼠标移入、移除
state = {mouse: false}
// 鼠标回调函数
handleMoouse = (flag) => {
return () => {
this.setState({mouse : flag})
}
}
// 任务复选框回调函数
hanleCheck = (id) => {
return (event) => {
this.props.updateTodo(id, event.target.checked)
}
}
// 删除按钮回调函数
handleDelete = (id) => {
if(window.confirm('确定删除吗?')){
this.props.deleteTodo(id)
}
}
render() {
const { id, name, done } = this.props
const {mouse} = this.state
return (
<li style={{backgroundColor: mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMoouse(true)} onMouseLeave={this.handleMoouse(false)}>
<label>
<input type="checkbox" checked={done} onChange = {this.hanleCheck(id)}/>
<span>{name}</span>
</label>
<button onClick={() => this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse ? 'block' : 'none'}}>删除</button>
</li>
)
}
}
List- index.css
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
List- index.jsx
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'
import './index.css'
export default class index extends Component {
// 参数类型与必要性限制
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodo: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
render() {
const { todos, updateTodo, deleteTodo } = this.props
return (
<ul className="todo-main">
{
todos.map((todo) => {
return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
})
}
</ul>
)
}
}
App.css
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
App.jsx
import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
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: true }
]
}
// 添加 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: 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 = (check) => {
const { todos } = this.state
const newTodos = todos.map((todoObj) => {
return { ...todoObj, done: check }
})
this.setState({ todos: newTodos })
}
// 清除所有已完成的 todo
clearAllTodo = () => {
const { todos } = this.state
const newTodos = todos.filter((todoObj) => {
return !todoObj.done
})
this.setState({ todos: newTodos })
}
render() {
const { todos } = this.state
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
<Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllTodo={this.clearAllTodo}/>
</div>
</div>
)
}
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App/>, document.getElementById('root'))
React Ajax
1.react脚手架配置代理总结
(1) 方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
(2) 方法二
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
2.React 代理配置案例
App.jsx
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
getStudentData = () => {
axios.get('http://localhost:3000/api1/students').then(
response => {console.log('成功了', response.data)},
error => {console.log('失败了', error)}
)
}
getCarData = () => {
axios.get('http://localhost:3000/api2/cars').then(
response => {console.log('成功了', response.data)},
error => {console.log('失败了', error)}
)
}
render() {
return (
<div>
<button onClick={this.getStudentData}>点击获取学生数据</button>
<button onClick={this.getCarData}>点击获取汽车数据</button>
</div>
)
}
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'
ReactDOM.render(<App/>, document.getElementById('root'))
setupProxy.js
// const proxy = require('http-proxy-middleware')
// module.exports = function(app){
// app.use(
// proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
// target:'http://localhost:5000', //请求转发给谁
// changeOrigin:true,//控制服务器收到的请求头中Host的值
// pathRewrite:{'^/api1':''} //重写请求路径(必须)
// }),
// proxy('/api2',{
// target:'http://localhost:5001',
// changeOrigin:true,
// pathRewrite:{'^/api2':''}
// }),
// )
// }
// 高版本
const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装
module.exports = function(app){
app.use(
proxy.createProxyMiddleware('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
target:'http://localhost:5000', //请求转发给谁
changeOrigin:true,//控制服务器收到的请求头中Host的值
pathRewrite:{'^/api1':''} //重写请求路径,下面有示例解释
}),
proxy.createProxyMiddleware('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2':''}
}),
)
}
3.GitHub 搜索案例-使用父组件的状态传递数据-axios
项目目录:
代码:
List-index.css
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
}
.card {
float: left;
width: 33.333%;
padding: .75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
}
.card > img {
margin-bottom: .75rem;
border-radius: 100px;
}
.card-text {
font-size: 85%;
}
List-index.jsx
import React, { Component } from 'react'
import './index.css'
export default class List extends Component {
render() {
const {isFirst, isLoading, users, err} = this.props
return (
<div className="row">
{
isFirst ? <h2>点击搜索按钮查询用户数据</h2> :
isLoading ? <h2>Loading ...</h2> :
err ? <h2 style={{color:'red'}}>{err}</h2> :
users.map((user) => {
return (
<div key={user.id} className="card">
<a rel="noreferrer" href={user.html_url} target="_blank">
<img alt="can't load" src={user.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{user.login}</p>
</div>
)
})
}
</div>
)
}
}
Search-index.jsx
import React, { Component } from 'react'
import axios from 'axios'
export default class Search extends Component {
search = () => {
// 获取到输入框中输入的内容
const { keyWordElement: { value: keyWord } } = this
const { updateAppState } = this.props
// 发送请求前更新状态
updateAppState({ isFirst: false, isLoading: true })
// 发送请求
axios.get(`http://localhost:3000/api/search/users?q=${keyWord}`).then(
// 发送请求后更新状态
response => {
updateAppState({ isLoading: false, users: response.data.items })
},
error => {
updateAppState({ isLoading: false, err: error })
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">Search Github Users</h3>
<div>
<input ref={(c) => { this.keyWordElement = c }} type="text" placeholder="enter the name you search" /> <button onClick={this.search}>Search</button>
</div>
</section>
)
}
}
App.jsx
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
state = {
users:[],
isFirst: true,
isLoading: false,
err: ''
}
updateAppState = (stateObj) => {
this.setState(stateObj)
}
render() {
return (
<div id="app">
<div className="container">
<Search updateAppState={this.updateAppState}/>
<List {...this.state}/>
</div>
</div>
)
}
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'
ReactDOM.render(<App/>, document.getElementById('root'))
setupProxy.js
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
proxy.createProxyMiddleware('/api', {
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite:{'^/api' : ''}
})
)
}
4.GitHub 搜索案例-发布订阅传递兄弟组件(适用于所有组件之间)数据-pubsub
与上面的案例只有以下几个文件存在差异:
Search-index.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'
export default class Search extends Component {
search = () => {
// 获取到输入框中输入的内容
const { keyWordElement: { value: keyWord } } = this
// 发送请求前更新状态
PubSub.publish('test message', { isFirst: false, isLoading: true })
// 发送请求
axios.get(`http://localhost:3000/api/search/users?q=${keyWord}`).then(
// 发送请求后更新状态
response => {
PubSub.publish('test message', { isLoading: false, users: response.data.items })
},
error => {
PubSub.publish('test message', { isLoading: false, err: error })
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">Search Github Users</h3>
<div>
<input ref={(c) => { this.keyWordElement = c }} type="text" placeholder="enter the name you search" /> <button onClick={this.search}>Search</button>
</div>
</section>
)
}
}
List-index.jsx
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
export default class List extends Component {
state = {
users:[],
isFirst: true,
isLoading: false,
err: ''
}
componentDidMount(){
this.token = PubSub.subscribe("test message", (_, stateObj) => {
this.setState(stateObj)
})
}
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
render() {
const {isFirst, isLoading, users, err} = this.state
return (
<div className="row">
{
isFirst ? <h2>点击搜索按钮查询用户数据</h2> :
isLoading ? <h2>Loading ...</h2> :
err ? <h2 style={{color:'red'}}>{err}</h2> :
users.map((user) => {
return (
<div key={user.id} className="card">
<a rel="noreferrer" href={user.html_url} target="_blank">
<img alt="can't load" src={user.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{user.login}</p>
</div>
)
})
}
</div>
)
}
}
App.jsx
import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
render() {
return (
<div id="app">
<div className="container">
<Search />
<List />
</div>
</div>
)
}
}
1.使用 fetch 发送 ajax 请求,它的级别和 xhr 是相同的,不需要引入额外的依赖
(1)最基础的版本
fetch(url).then(
response => {
console.log('联系服务器成功了');
// 此处返回的是一个 Promise 对象
return response.json()
},
error => {
console.log('联系服务器失败了', error)
return new Promise(() => {})
}
).then(
response => {console.log('获取数据成功了', response)},
error => {console.log('获取数据失败了', error)}
)
(2)改进版本,将 error 整合到一起
fetch(url).then(
response => {
console.log('联系服务器成功了');
// 此处返回的是一个 Promise 对象
return response.json()
}
).then(
response => {console.log('获取数据成功了', response)},
).catch(
(error) => {console.log('发送请求失败', error)}
)
(3)改进版本,不用多个 then,使用 await 【await关键字是异步编程中用于等待 Promise 对象解决的一种机制】
try{
const response = await fetch(url)
const data = await response.json()
console.log('获取数据成功了', data)
}catch(error){
console.log('发送请求失败', error)
}