尚硅谷React教程
(01_尚硅谷_react简介&)
01_尚硅谷_react简介&


02_尚硅谷_react的基本使用
(DOM Rendering&Babel)
DOM Rendering&Babel

Babel支持jsx扩展语法,转为js代码

谷歌浏览器扩展文件



<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"> //必须声明babel
// 创建虚拟DOM元素
const vDom = <h1>Hello React</h1> // 千万不要加引号
// 渲染虚拟DOM到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById('test'))
</script>
</body>
03_尚硅谷_jsx理解和基本使用
(React的JSX语言&toLowerCase函数&createElement用法&实战:使用Render函数开发可排序的表格组件&使用代替模板功能)
React的JSX语言&toLowerCase函数&createElement用法&实战:使用Render函数开发可排序的表格组件&使用代替模板功能



大括号动态的值传入进去,不是简单文本

<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">
// 1. 创建虚拟DOM
/*方式一: 纯JS(一般不用)创建虚拟DOM元素对象*/
const msg = 'I Like You!'
const myId = 'Atguigu'
const vDOM1 = React.createElement('h2', {id: myId}, msg.toUpperCase())
// 2. 渲染到真实页面
const domContainer = document.getElementById('test1')
// debugger
ReactDOM.render(vDOM1, domContainer)
</script>
<script type="text/babel">
// 1. 创建虚拟DOM
/*方式二: JSX创建虚拟DOM元素对象*/
const vDOM2 = <h3 id={myId.toUpperCase()}>{msg.toLowerCase()}</h3>
// debugger
// 2. 渲染到真实页面
ReactDOM.render(vDOM2, document.getElementById('test2'))
</script>
04_尚硅谷_jsx练习
(map&JSX实战之ReactJS)
map&JSX实战之ReactJS
把标签和js混在一起,

<script type="text/babel">
/*
功能: 动态展示列表数据
*/
/*
技术点:
1). 使用JSX创建虚拟DOM
2). React能自动遍历显示数组中所有的元素
3). array.map()的使用
*/
// 数据的数组
var names = ['jquery', 'zeptoo', 'angular', 'react全家桶', 'vue全家桶']
// 数据的数组-->标签的数组
var lis = []
names.forEach((name, index) => lis.push(<li key={index}>{name}</li>))
// 创建虚拟DOM
const ul = <ul>{lis}</ul>
// 渲染虚拟DOM
ReactDOM.render(ul, document.getElementById('example1'))
const ul2 = <ul>{
names.map((name, index) => <li key={index}>{name}</li>)
}</ul>
ReactDOM.render(ul2, document.getElementById('example2'))
</script>
05_尚硅谷_模块与组件的理解
( ES6模块的循环加载&Redux 组件)
ES6模块的循环加载&Redux 组件

06_尚硅谷_react组件的基本定义和使用
(把数据添加到组件中&加载组件&产品分类组件&React-Redux的should-ComponentUpdate实现)
把数据添加到组件中&加载组件&产品分类组件&React-Redux的should-ComponentUpdate实现
最终渲染<MyCompone/> 方法一定要return,没有直接调用函数,渲染时调用函数。

只有实例能调用方法,

<script type="text/babel">
// 1. 定义组件
/*方式1: 工厂函数组件(简单组件)*/
function MyComponent () {
return <h2>工厂函数组件(简单组件)</h2>
}
/*方式2: ES6类组件(复杂组件)*/
class MyComponent2 extends React.Component {
render () {
console.log(this) // MyComponent2的实例对象
return <h2>ES6类组件(复杂组件)</h2>
}
}
// 2. 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
ReactDOM.render(<MyComponent2 />, document.getElementById('example2'))
</script>
07_尚硅谷_组件三大属性(1)_state
(组件的生命周期&state&带状态的文本框组件&关于this对象&使用bind()方法绑定事件)
组件的生命周期&state&带状态的文本框组件&关于this对象&使用bind()方法绑定事件
this是组件对象


不用直接操作dom,this是组件对象

Props参数,this.state读取状态,解构赋值不用点

onClick事件监听,this是组件对象,调用组件对象的方法,setState是固定得到方法设置新的状态对象。左右相同名字可以省略

重写组件类render方法,新增handleClick内部this默认是undefined,让this要指向组件对象。

点击的时候是通过bind产生新的handleClick函数,只是函数体一模一样

也可以在onClick中 绑定,onclick绑定的是band产生的新的函数,这种写法效率低重写渲染。

<script type="text/babel">
/*
需求: 自定义组件, 功能说明如下
1. 显示h2标题, 初始文本为: 你喜欢我
2. 点击标题更新为: 我喜欢你
*/
class Like extends React.Component {
constructor (props) {
super(props)
// 初始化状态
this.state = {
isLikeMe: true
}
// 绑定this为组件对象
this.change = this.change.bind(this)
}
change () {
// 更新状态: this.setState()
// this.state.isLikeMe = !this.state.isLikeMe // 不能更新更新某个状态
this.setState({
isLikeMe: !this.state.isLikeMe
})
}
render () {
console.log('render()')
const text = this.state.isLikeMe ? '你喜欢我' : '我喜欢你'
return <h2 onClick={this.change}>{text}</h2>
}
}
ReactDOM.render(<Like />, document.getElementById('example'))
</script>
08_尚硅谷_组件三大属性(2)_props
(React的props和state&getDefaultProps &propTypes)
React的props和state&getDefaultProps &propTypes
组件有state状态不能使用工厂函数模式,通过标签属性传递属性。
标签属性名要和props后面的属性值一致






“”是字符串{}是数字18
![]()
![]()
传递多个属性,三点运算符


组件对象自带props,一旦传递了数据会自动渲染

三点运算符...


<script type="text/babel">
/*
需求: 自定义用来显示一个人员信息的组件, 效果如页面. 说明
1). 如果性别没有指定, 默认为男
2). 如果年龄没有指定, 默认为18
*/
//1. 定义组件类
class Person extends React.Component {
render() {
console.log(this)
return (
<ul>
<li>姓名: {this.props.name}</li>
<li>性别: {this.props.sex}</li>
<li>年龄: {this.props.age}</li>
</ul>
)
}
}
// 对标签属性进行限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number
}
// 指定属性的默认值
Person.defaultProps = {
sex: '男',
age: 18
}
//2. 渲染组件标签
const person = {
name: 'Tom',
sex: '女',
age: 18
}
ReactDOM.render(<Person {...person}/>, document.getElementById('example1'))
const person2 = {
myName: 'JACK',
age: 17
}
ReactDOM.render(<Person name={person2.myName} age={person2.age}/>,
document.getElementById('example2'))
</script>
09_尚硅谷_组件三大属性(3)_refs和事件处理
(组件三大属性(3)_refs和事件处理&)
组件三大属性(3)_refs和事件处理&

不能getElementById获取input输入框,ref是input的标识

标识ref名称content是对象的属性名

Ref后面跟着回调函数,第一次渲染时执行,把当前的input绑定到组件对象this上,直接通过this获取input输入框

处理事件,所有的方法都需要bind万无一失,操作dom元素是发生事件的元素event


ref是一个回调函数,input当前的input绑定到this组件对象上
<script type="text/babel">
/*
需求: 自定义组件, 功能说明如下:
1. 界面如果页面所示
2. 点击按钮, 提示第一个输入框中的值
3. 当第2个输入框失去焦点时, 提示这个输入框中的值
*/
//定义组件
class MyComponent extends React.Component {
constructor(props) {
super(props) // 调用父类(Component)的构造函数
//console.log(this)
// 将自定义的函数强制绑定为组件对象
this.handleClick = this.handleClick.bind(this) // 将返回函数中的this强制绑定为指定的对象, 并没有改变原来的函数中的this
}
// 自定义的方法中的this默认为null
handleClick () {
// alert(this) //this默认是null, 而不是组件对象
// 得到绑定在当前组件对象上的input的值
alert(this.msgInput.value)
}
handleBlur (event) {
alert(event.target.value)
}
render () {
return (
<div>
<input type="text" ref={input => this.msgInput = input}/>{' '}
<button onClick={this.handleClick}>提示输入数据</button>{' '}
<input type="text" placeholder="失去焦点提示数据" onBlur={this.handleBlur}/>
</div>
)
}
}
// 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example'))
</script>
10_尚硅谷_组件组合使用_初始化显示
(组件组合使用_初始化显示&)
组件组合使用_初始化显示&
状态为组件内部的状态,state是变化的,props是组件外部传入的属性,一个组件要显示数据可以由内部维护的数据state,一种是外部标签传入的数据,改变状态必须用一个方法setState。
复杂页面开发流程,外面根组件,列表组件


Add和List是App的子组件,只用渲染App组件即可。

![]()
组件化编码实现静态页面
只能有一个div根标签,

实现数据的动态化显示,数据保存在哪个组件内,数据在哪些组件要使用。两个组件都需要使用,放在App共同父组件中。

初始化要交给list显示,动态{}大括号传递数据,this.state读状态

<ul>里先来一个大括号,需要一个标签数组需要map方法,

解构赋值

<Add/>组件要传递一个长度数据传递给Add,重复提出来

数据在props属性里面,不在状态里

11_尚硅谷_组件组合使用_交互
(组件组合使用_交互&)
组件组合使用_交互&
一旦有大括号必须有return

一旦有一个新的函数,需要在构造器中定义

子组件改变父组件的状态,setState,状态在哪个组件,更新状态的行为就应该定义在哪个组件里。
传递todo参数,不能直接更新状态,必须通过setState更新,否则无法更新界面。

函数也是数据,可以当做一般数据传递函数,Add组件需要直接传递过去,名字一致不易出错。
不写this当前作用域找,this在组件对象中找

声明接收新的属性,接收addTodo字符串,传过来的属性在props中


This不对,一定要绑定this否则undefined,清除输入数据。只要自己定义的方法必须要加bind


子组件改变父组件的状态,状态在哪个组件,更新状态的行为就应该在哪个组件
<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/prop-types.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
/*
1)拆分组件: 拆分界面,抽取组件
2)实现静态组件: 使用组件实现静态页面效果
3)实现动态组件
① 动态显示初始化数据
② 交互功能(从绑定事件监听开始)
*/
// 应用组件
class App extends React.Component {
constructor (props) {
super(props)
// 初始化状态
this.state = {
todos: ['吃饭', '睡觉', '打豆豆']
}
this.add = this.add.bind(this)
}
add (todo) {
const {todos} = this.state
todos.unshift(todo)
//更新状态
this.setState({todos})
}
render () {
const {todos} = this.state
return (
<div>
<TodoAdd add={this.add} count={todos.length} />
<TodoList todos={todos} />
</div>
)
}
}
// 添加todo组件
class TodoAdd extends React.Component {
constructor (props) {
super(props)
this.addTodo = this.addTodo.bind(this)
}
addTodo () {
// 读取输入数据
const text = this.input.value.trim()
// 查检
if(!text) {
return
}
// 保存到todos
this.props.add(text)
// 清除输入
this.input.value = ''
}
render () {
return (
<div>
<h2>Simple TODO List</h2>
<input type="text" ref={input => this.input=input}/>
<button onClick={this.addTodo}>Add #{this.props.count}</button>
</div>
)
}
}
TodoAdd.propTypes = {
add: PropTypes.func.isRequired,
count: PropTypes.number.isRequired
}
// todo列表组件
class TodoList extends React.Component {
render () {
const {todos} = this.props
return (
<ul>
{
todos.map((todo, index) => <li key={index}>{todo}</li>)
}
</ul>
)
}
}
TodoList.propTypes = {
todos: PropTypes.array.isRequired
}
// 渲染应用组件标签
ReactDOM.render(<App />, document.getElementById('example'))
</script>
12_尚硅谷_组件组合使用_总结
(组件组合使用_总结&)
组件组合使用_总结&
13_尚硅谷_组件_收集表单数据
(组件_收集表单数据&)
组件_收集表单数据&
交互现做绑定事件,
![]()



构造器中初始化状态值,输入进去没有变又回到以前数据

原生dom的onChange,setState的名字要和state中的名字pwd一样

不用再去dom中取值,不用大括号,大括号点后面的要删除省略了

受控组件直接读状态state,自动收集数据,非受控自己获取input值


非受控手动去读输入框读取数据会直接操作DOM,受控直接读state状态自动会收集状态

<script type="text/babel">
/*
1. 问题: 在react应用中, 如何收集表单输入数据
2. 包含表单的组件分类
受控组件
非受控组件
*/
/*
需求: 自定义包含表单的组件
1. 界面如下所示
2. 输入用户名密码后, 点击登陆提示输入信息
3. 不提交表单
*/
class LoginForm extends React.Component {
constructor(props) {
super(props)
this.state = {
username: ''
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({username: event.target.value})
}
handleSubmit(event) {
alert(`准备提交的用户名为: ${this.state.username}, 密码:${this.pwdInput.value}`)
// 阻止事件的默认行为: 提交表单
event.preventDefault()
}
render () {
return (
<form onSubmit={this.handleSubmit} action="/test">
<label>
用户名:
<input type="text" value={this.state.username} onChange={this.handleChange} />
</label>
<label>
密码:
<input type="password" ref={(input) => this.pwdInput = input} />
</label>
<input type="submit" value="登陆" />
</form>
)
}
}
ReactDOM.render(<LoginForm />, document.getElementById('example'))
</script>
14_尚硅谷_组件_生命周期
(组件_生命周期&)
组件_生命周期&

传递文本msg,通过props显示

样式是键值对,外围的大括号代码里面要写js代码,里面的大括号代码我是一个js对象,冒号前是样式名,后面是值。

定时器渲染组件,组件的生命周期,初始化的时候启动定时器,只用启动一次即可。

Render第一次渲染显示,

最终要更新状态,this是函数里面的this,是内置声明周期的函数,相当于重写。
交给setInterval的不是function而是bind产生的新的函数。

小数加减法不是刚好为0




移除组件


组件移除时回调


<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
/*
需求: 自定义组件
1. 让指定的文本做显示/隐藏的动画
2. 切换时间为2S
3. 点击按钮从界面中移除组件界面
*/
class Fade extends React.Component {
constructor (props) {
super(props)
console.log('constructor(): 创建组件对象')
this.state = {
opacity: 1
}
this.removeComponent = this.removeComponent.bind(this)
}
componentWillMount () {
console.log('componentWillMount(): 初始化将要挂载')
}
// 组件渲染后调用
componentDidMount () {// 在此方法中启动定时器/绑定监听/发送ajax请求
console.log('componentDidMount(): 初始化已经挂载')
// 保存到当前组件对象中
this.intervalId = setInterval(function () {
console.log('--------')
// 得到当前opacity
let {opacity} = this.state
// 更新opacity
opacity -= 0.1
if(opacity<=0) {
opacity = 1
}
// 更新状态
this.setState({opacity})
}.bind(this), 200)
}
componentWillUpdate () {
console.log('componentWillUpdate(): 将要更新')
}
componentDidUpdate () {
console.log('componentDidUpdate(): 已经更新')
}
componentWillUnmount () {// 清除定时器/解除监听
console.log('componentWillUnmount(): 将要被移除')
clearInterval(this.intervalId)
}
removeComponent () {
ReactDOM.unmountComponentAtNode(document.getElementById('example'))
}
render() {
console.log('render() 渲染组件')
return (
<div>
<h2 style={{opacity:this.state.opacity}}>{this.props.content}</h2>
<button onClick={this.removeComponent}>不活了</button>
</div>
)
}
}
ReactDOM.render(<Fade content="react学不会, 怎么办?"/>, document.getElementById('example'))
</script>
15_尚硅谷_组件_生命周期总结
(组件_生命周期总结&)
组件_生命周期总结&

16_尚硅谷_虚拟DOM和DOM diff算法
(虚拟DOM和DOM diff算法&)
虚拟DOM和DOM diff算法&



17_尚硅谷_react脚手架应用分析
(react脚手架应用分析&)
react脚手架应用分析&
18_尚硅谷_使用react脚手架创建应用
(使用react脚手架创建应用&)
使用react脚手架创建应用&
19_尚硅谷_基于脚手架项目编写应用
(基于脚手架项目编写应用&)
基于脚手架项目编写应用&
所有组件都放在components文件夹里,组件是jsx文件

要暴露组件,默认暴露引用不用加大括号,方便

写一条语句

引入图片大括号

入口文件index.js,引入React和渲染ReactDOM,最终要渲染App组件标签,可省略后缀


对应用中所有模块编译打包运行,

引入样式模块,自定义模块必须以 ./或者../开头


20_尚硅谷_练习1_评论管理_拆分组件与实现静态组件
(练习1_评论管理_拆分组件与实现静态组件&)
练习1_评论管理_拆分组件与实现静态组件&



把div拷贝到app.jsx中

必须要有结束标签
![]()
修改静态文件的class为className
![]()
![]()
Style必须两个大括号,js代码要有引用不然是变量了
![]()
拆分为多个组件中



组件文件夹下创建一个css


21_尚硅谷_练习1_评论管理_初始化数据动态显示
(练习1_评论管理_初始化数据动态显示&)
练习1_评论管理_初始化数据动态显示&
评论多个组件需要放在父组件上面,App是组件类,组件对象是this,不用写constructor

把属性传递过去,最好前面定义


传给commentList,读取属性前先得声明接收什么属性
下载--save运行时依赖,

![]()

给组件对象添加

给组件类添加

从属性里取东西,先取出来再说
初始化数据设置好属性名


在list中使用item组件

在大括号里写js代码,item已经声明好了名称必须使用comment

能一样名字尽量一样,不犯错误,{}括号里的要与形参一样

22_尚硅谷_练习1_评论管理_添加
(练习1_评论管理_添加&)
练习1_评论管理_添加&
交互,箭头函数没有自己的this,外围全局的this刚好是组件对象




绑定onChange监听,

读到里面的值,更新状态,setState和state名字一致。

This.sate是app.js父组件的属性
添加comment必须传递comment才能添加


调用addComment首先要声明

更新状态页面输入框状态就会发生改变




23_尚硅谷_练习1_评论管理_删除
(练习1_评论管理_删除&)
练习1_评论管理_删除&
Splice增删改都可以实现,删除index下标的元素

函数传递给list

在props最好先从props中取出来,

删除传递index
![]()


计算是否显示

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
<link rel="stylesheet" href="/css/bootstrap.css">
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
import React from 'react'
import CommentAdd from '../comment-add/comment-add'
import CommentList from '../comment-list/comment-list'
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
comments: []
}
this.delete = this.delete.bind(this)
}
componentDidMount () {
//模拟异步获取数据
setTimeout(() => {
const comments = [
{
username: "Tom",
content: "ReactJS好难啊!",
id: Date.now()
},
{
username: "JACK",
content: "ReactJS还不错!",
id: Date.now() + 1
}
]
this.setState({
comments
})
}, 1000)
}
add = (comment) => {
let comments = this.state.comments
comments.unshift(comment)
this.setState({ comments })
}
delete (index) {
let comments = this.state.comments
comments.splice(index, 1)
this.setState({ comments })
}
render () {
return (
<div>
<header className="site-header jumbotron">
<div className="container">
<div className="row">
<div className="col-xs-12">
<h1>请发表对React的评论</h1>
</div>
</div>
</div>
</header>
<div className="container">
<CommentAdd add={this.add}/>
<CommentList comments={this.state.comments} delete={this.delete}/>
</div>
</div>
)
}
}
export default App
import React from 'react'
import PropTypes from 'prop-types'
class CommentAdd extends React.Component {
constructor (props) {
super(props)
this.state = {
username: '',
content: ''
}
this.addComment = this.addComment.bind(this)
this.changeUsername = this.changeUsername.bind(this)
this.changeContent = this.changeContent.bind(this)
}
addComment () {
// 根据输入的数据创建评论对象
let { username, content } = this.state
let comment = { username, content }
// 添加到comments中, 更新state
this.props.add(comment)
// 清除输入的数据
this.setState({
username: '',
content: ''
})
}
changeUsername (event) {
this.setState({
username: event.target.value
})
}
changeContent (event) {
this.setState({
content: event.target.value
})
}
render () {
return (
<div className="col-md-4">
<form className="form-horizontal">
<div className="form-group">
<label>用户名</label>
<input type="text" className="form-control" placeholder="用户名"
value={this.state.username} onChange={this.changeUsername}/>
</div>
<div className="form-group">
<label>评论内容</label>
<textarea className="form-control" rows="6" placeholder="评论内容"
value={this.state.content} onChange={this.changeContent}></textarea>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-default pull-right" onClick={this.addComment}>提交</button>
</div>
</div>
</form>
</div>
)
}
}
CommentAdd.propTypes = {
add: PropTypes.func.isRequired
}
export default CommentAdd
import React from 'react'
import PropTypes from 'prop-types'
import CommentItem from '../comment-item/comment-item'
import './commentList.css'
class CommentList extends React.Component {
constructor (props) {
super(props)
}
render () {
let comments = this.props.comments
let display = comments.length > 0 ? 'none' : 'block'
return (
<div className="col-md-8">
<h3 className="reply">评论回复:</h3>
<h2 style={{ display: display }}>暂无评论,点击左侧添加评论!!!</h2>
<ul className="list-group">
{
comments.map((comment, index) => {
console.log(comment)
return <CommentItem comment={comment} key={index} index={index} delete={this.props.delete}/>
})
}
</ul>
</div>
)
}
}
CommentList.propTypes = {
comments: PropTypes.array.isRequired,
delete: PropTypes.func.isRequired
}
export default CommentList
import React from 'react'
import PropTypes from 'prop-types'
import './commentItem.css'
class CommentItem extends React.Component {
constructor (props) {
super(props)
this.deleteComment = this.deleteComment.bind(this)
}
deleteComment () {
let username = this.props.comment.username
if (window.confirm(`确定删除${username}的评论吗?`)) {
this.props.delete(this.props.index)
}
}
render () {
let comment = this.props.comment
return (
<li className="list-group-item">
<div className="handle">
<a href="javascript:" onClick={this.deleteComment}>删除</a>
</div>
<p className="user"><span >{comment.username}</span><span>说:</span></p>
<p className="centence">{comment.content}</p>
</li>
)
}
}
CommentItem.propTypes = {
comment: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
delete: PropTypes.func.isRequired
}
export default CommentItem
24_尚硅谷_ajax请求_使用axios
(ajax请求_使用axios&)
ajax请求_使用axios&
React项目发送Ajax请求

发送请求是异步的操作再初始化更新与死亡3阶段,尽快显示数据componentDidMount初始化一上来就发送ajax请求。


数据就在response里面,对象属性是固定的data

异常catch处理



25_尚硅谷_ajax请求_使用fetch
(ajax请求_使用fetch&)
ajax请求_使用fetch&
Fetch本身是promise风格尽量少嵌套,return返回的是一个promise对象,返回json对象



<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="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>
<script type="text/babel">
/*
需求:
1. 界面效果如下
2. 根据指定的关键字在github上搜索匹配的最受关注的库
3. 显示库名, 点击链接查看库
4. 测试接口: https://api.github.com/search/repositories?q=r&sort=stars
*/
class MostStarRepo extends React.Component {
constructor (props) {
super(props)
this.state = {
repoName: '',
repoUrl: ''
}
}
componentDidMount () {
const url = `https://api.github.com/search/repositories?q=${this.props.searchWord}&sort=stars`
// const url = `https://api.github.com/search/repositories2?q=${this.props.searchWord}&sort=stars`
axios.get(url)
.then(response => {
const result = response.data
console.log(result)
const repo = result.items[0]
this.setState({
repoName: repo.name,
repoUrl: repo.html_url
})
})
.catch(error => {
// debugger
console.log(error)
alert('请求失败 '+ error.message)
})
/*fetch(url, {method: "GET"})
.then(response => response.json())
.then(data => {
console.log(data)
if(data.message) {
alert(`请求失败: ${data.message}`)
} else {
const repo = data.items[0]
this.setState({
repoName: repo.name,
repoUrl: repo.html_url
})
}
})*/
}
render () {
const {repoName, repoUrl} = this.state
if(!repoName) {
return <h2>loading...</h2>
} else {
return (
<h2>
most star repo is <a href={repoUrl}>{repoName}</a>
</h2>
)
}
}
}
ReactDOM.render(<MostStarRepo searchWord="r"/>, document.getElementById('example'))
</script>
26_尚硅谷_练习2_用户搜索_初始化显示
(练习2_用户搜索_初始化显示&)
练习2_用户搜索_初始化显示&

不用洗px单位
![]()

App引入子组件

搜索有4个状态

Div有多个是个数组,

27_尚硅谷_练习2_用户搜索_交互
(练习2_用户搜索_交互&)
练习2_用户搜索_交互&
发请求获取对应的数据,在哪个组件发请求,发请求前后都要更新main的状态,所以在main组件发送请求。

点击button调用方法setSearch方法

调用方法出入input里面的内容,非受控组件ref,调用setSearch方法,出入searchName

点击搜索更新app.js中的state,状态需要让main知道,传递给main

Main.js根据不同searchName发送请求


数据没有变化需要更新状态,不报错的bug

知道接收到的属性发生了变化

新得到属性在参数中,当组件接收到新的属性时调用
![]()


最后要返回一个对象,大括号是一个函数体需要加一个小括号表示一个对象。

更新状态



兄弟组件search将searchName传递给List,点击搜索按钮告诉父亲组件更新state,由父亲组件通过属性传递给子组件,一旦searchName发送变化就会通知list重新渲染

import React from 'react'
import Search from './search'
import UserList from './user-list'
export default class App extends React.Component {
state = {
searchName: ''
}
refreshName = (searchName) => this.setState({searchName})
render() {
return (
<div className="container">
<section className="jumbotron">
<h3 className="jumbotron-heading">Search Github Users</h3>
<Search refreshName={this.refreshName}/>
</section>
<UserList searchName={this.state.searchName}/>
</div>
)
}
}
/**
* 上部的搜索模块
*/
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class Search extends Component {
static propTypes = {
refreshName: PropTypes.func.isRequired
}
search = () => {
var name = this.nameInput.value
this.props.refreshName(name)
}
render() {
return (
<div>
<input type="text" placeholder="enter the name you search"
ref={(input => this.nameInput = input)}/>
<button onClick={this.search}>Search</button>
</div>
)
}
}
export default Search
list要知道接收到的属性发生了变化,通过componentWillReceiveProps组件将要接收到新的props执行
/**
* 下部的用户列表模块
*/
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
class UserList extends React.Component {
static propTypes = {
searchName: PropTypes.string.isRequired
}
state = {
firstView: true,
loading: false,
users: null,
error: null
}
async componentWillReceiveProps(nextProps) {
let searchName = nextProps.searchName
console.log('发送ajax请求', searchName)
const url = `https://api.github.com/search/users?q=${searchName}`
this.setState({ firstView: false, loading: true })
// 使用axios库
axios.get(url)
.then((response) => {
console.log(response)
this.setState({ loading: false, users: response.data.items })
})
.catch((error)=>{
// debugger
console.log('error', error.response.data.message, error.message)
this.setState({ loading: false, error: error.message })
})
try {
const result = await axios.get(url)
this.setState({ loading: false, users: result.data.items })
} catch(err) {
// debugger
console.log('----', err.message)
}
}
render () {
if (this.state.firstView) {
return <h2>Enter name to search</h2>
} else if (this.state.loading) {
return <h2>Loading result...</h2>
} else if (this.state.error) {
return <h2>{this.state.error}</h2>
} else {
return (
<div className="row">
{
this.state.users.map((user) => (
<div className="card" key={user.html_url}>
<a href={user.html_url} target="_blank">
<img src={user.avatar_url} style={{width: '100px'}} alt='user'/>
</a>
<p className="card-text">{user.login}</p>
</div>
))
}
</div>
)
}
}
}
export default UserList
28_尚硅谷_组件间通信的2种方式
(组件间通信的2种方式&)
组件间通信的2种方式&



![]()
兄弟之间通信还要借助父亲,search组件要发布消息通知所有关注的人。
订阅相当于绑定监听,要绑定事件名与回调函数,发布消息事件名与携带的数据

发送search名的消息,search发布消息

Main,comment-list要订阅消息,调用回调函数第一个参数消息名,第二个参数传递的数据根据实际接收数据的意义取名。

一旦回调函数都写箭头函数,回调函数的this肯定不是组件对象。



函数要不断地传递


去掉就没有必要声明,


发生事件的地方才需要发布消息,点击了发生了事件所以要发布消息。传递多个数据封装成对象。
comment-item发布消息

App里面要订阅消息,绑定监听需要在componentDidMount一上来就要绑定好
app.js订阅消息

29_尚硅谷_react-router说明
(react-router说明&)
react-router说明&
页面某些部分发生了变化,整个应用只有一个完整页面


前台路由就是点击显示一个页面


点击调用函数,return false不会发送http请求。





<p><input type="text"></p>
<a href="/test1" onclick="return push('/test1')">test1</a><br><br>
<button onClick="push('/test2')">push test2</button><br><br>
<button onClick="back()">回退</button><br><br>
<button onClick="forword()">前进</button><br><br>
<button onClick="replace('/test3')">replace test3</button><br><br>
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
let history = History.createBrowserHistory() // 方式一
history = History.createHashHistory() // 方式二
// console.log(history)
function push (to) {
history.push(to)
return false
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
function replace (to) {
history.replace(to)
}
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
30_尚硅谷_react-router基本使用
(react-router基本使用&)
react-router基本使用&


点击连接页面不刷新,路由组件

Web版本-dom
![]()
分为路由组件view与非路由组件两个文件夹


3个组件

用react-router不能直接渲染App要用一个东西包起立,管理整个应用

Router管理整个应用

两个导航连接不应该用a标签,

最终转化为a标签
![]()

只能显示其中一个路由组件




![]()
一上来空的
![]()

switch匹配才会显示,to和path要duiying
import React from 'react'
import {Route, Switch, Redirect} from 'react-router-dom'
import MyNavLink from './my-nav-link'
import About from '../views/about'
import Home from '../views/home'
export default class App extends React.Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/*导航路由链接*/}
<MyNavLink className="list-group-item" to='/about'>About</MyNavLink>
<MyNavLink className="list-group-item" to='/home'>Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/*可切换的路由组件*/}
<Switch>
<Route path='/about' component={About}/>
<Route path='/home' component={Home}/>
<Redirect to='/about'/>
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
import React from 'react'
export default function About() {
return <div>About组件内容</div>
}
import React from 'react'
import {Switch, Route, Redirect} from 'react-router-dom'
import MyNavLink from '../components/my-nav-link'
import News from './news'
import Message from './message'
export default function Home() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to='/home/news'>News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
<Switch>
<Route path='/home/news' component={News} />
<Route path='/home/message' component={Message} />
<Redirect to='/home/news'/>
</Switch>
</div>
</div>
)
}
import React from 'react'
export default class News extends React.Component {
state = {
newsArr: ['news001', 'news002', 'news003']
}
render () {
return (
<div>
<ul>
{
this.state.newsArr.map((news, index) => <li key={index}>{news}</li>)
}
</ul>
</div>
)
}
}
import React from 'react'
import {Link, Route} from 'react-router-dom'
import MessageDetail from "./message-detail"
export default class Message extends React.Component {
state = {
messages: []
}
componentDidMount () {
// 模拟发送ajax请求
setTimeout(() => {
const data = [
{id: 1, title: 'Message001'},
{id: 3, title: 'Message003'},
{id: 6, title: 'Message006'},
]
this.setState({
messages: data
})
}, 1000)
}
ShowDetail = (id) => {
this.props.history.push(`/home/message/${id}`)
}
ShowDetail2 = (id) => {
this.props.history.replace(`/home/message/${id}`)
}
back = () => {
this.props.history.goBack()
}
forward = () => {
this.props.history.goForward()
}
render () {
const path = this.props.match.path
return (
<div>
<ul>
{
this.state.messages.map((m, index) => {
return (
<li key={index}>
<Link to={`${path}/${m.id}`}>{m.title}</Link>
<button onClick={() => this.ShowDetail(m.id)}>查看详情(push)</button>
<button onClick={() => this.ShowDetail2(m.id)}>查看详情(replace)</button>
</li>
)
})
}
</ul>
<p>
<button onClick={this.back}>返回</button>
<button onClick={this.forward}>前进</button>
</p>
<hr/>
<Route path={`${path}/:id`} component={MessageDetail}></Route>
</div>
)
}
}
import React from 'react'
const messageDetails = [
{id: 1, title: 'Message001', content: '我爱你, 中国'},
{id: 3, title: 'Message003', content: '我爱你, 老婆'},
{id: 6, title: 'Message006', content: '我爱你, 孩子'},
]
export default function MessageDetail(props) {
const id = props.match.params.id
const md = messageDetails.find(md => md.id===id*1)
return (
<ul>
<li>ID: {md.id}</li>
<li>TITLE: {md.title}</li>
<li>CONTENT: {md.content}</li>
</ul>
)
}
31_尚硅谷_NavLink组件包装优化
(NavLink组件包装优化&)
NavLink组件包装优化&
传递属性到NavLink中,内部控制当前选中样式


import React from 'react'
import {NavLink} from 'react-router-dom'
export default function MyNavLink(props) {
return <NavLink {...props} activeClassName='activeClass'/>
}
32_尚硅谷_嵌套路由
(嵌套路由&)
嵌套路由&
路由组件又包含了路由,二级路由

指定路径是home下面的news

![]()
模拟发送ajax请求更新状态,只要回调函数都用箭头函数,


在Home组件中显示,路由链接to指定路径,在Route路由组件中展现只显示一种可能性

![]()



默认自动显示

33_尚硅谷_向路由组件传递数据
(向路由组件传递数据&)
向路由组件传递数据&
向组件传递数据props,不是标签没办法传递数据

点击不同的链接根据不同id查询内容
![]()
写js代码需要用大括号括起来,用反引号或者拼串方式动态拼接,:id占位符标识名称

请求路径和route匹配最终显示出来。


看哪个里面有id



用的是函数不是组件类的形式,参数接收props

Find方法返回第一个结果为true的元素,m就是find的结果。*1变为数字

![]()

34_尚硅谷_路由链接与非路由链接说明
(路由链接与非路由链接说明&)
路由链接与非路由链接说明&
![]()
路由链接不发送请求
![]()
路由链接必须用NavLink或者Route,替换a标签

35_尚硅谷_2种路由跳转的方式
(2种路由跳转的方式&)
2种路由跳转的方式&
不知道点击的是哪一个按钮,调用push方法传入id值,没有调用不能传递参数

Onclick函数点击执行,向回调函数里面传递参数

Replace查看,区别在于回退的时候效果不一样。



36_尚硅谷_react-ui_antd
(react-ui_antd&)
react-ui_antd&



放在index.html中





启动服务器和客户端
![]()
![]()
按需打包



编译打包的工具包

import React, {Component} from 'react'
// 分别引入需要使用的组件
// import Button from 'antd-mobile/lib/button'
// import Toast from 'antd-mobile/lib/toast'
import {Button, Toast} from 'antd-mobile'
export default class App extends Component {
handleClick = () => {
Toast.info('提交成功', 2)
}
render() {
return (
<div>
<Button type="primary" onClick={this.handleClick}>提交</Button>
</div>
)
}
}
37_尚硅谷_redux_理解
(redux_理解&)
redux_理解&

多个组件共享状态,状态在哪里修改状态的行为就定义在哪个组件里,集中式的管理状态。
单应用有很多组件,

通过store对象来存储状态,dispatch分发通知store更新状态流程


38_尚硅谷_redux_counter应用_react版本
(redux_counter应用_react版本&)
redux_counter应用_react版本&


![]()
动态组件先初始化状态显示,数据名称类型位置,变化的数据
![]()

绑定事件监听,使用箭头函数this才会是组件对象






import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import App from './components/app/app'
import store from './redux/store'
// 定义渲染根组件标签的函数
ReactDOM.render(
(
<Provider store={store}>
<App/>
</Provider>
),
document.getElementById('root')
)
import React, {Component} from 'react'
export default class App extends Component {
state = {
count: 0
}
increment = () => {
const num = this.refs.numSelect.value*1
const count = this.state.count + num
this.setState({count})
}
decrement = () => {
const num = this.refs.numSelect.value*1
const count = this.state.count - num
this.setState({count})
}
incrementIfOdd = () => {
let count = this.state.count
if(count%2==1) {
const num = this.refs.numSelect.value*1
count += num
this.setState({count})
}
}
incrementAsync = () => {
setTimeout(() => {
const num = this.refs.numSelect.value*1
const count = this.state.count + num
this.setState({count})
}, 1000)
}
render () {
const {count} = this.state
return (
<div>
<p>
click {count} times {' '}
</p>
<select ref="numSelect">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>{' '}
<button onClick={this.increment}>+</button>{' '}
<button onClick={this.decrement}>-</button>{' '}
<button onClick={this.incrementIfOdd}>increment if odd</button>{' '}
<button onClick={this.incrementAsync}>increment async</button>
</div>
)
}
}

39_尚硅谷_redux_counter应用_redux版本
(redux_counter应用_redux版本&)
redux_counter应用_redux版本&
下载依赖包
![]()

Reducer根据老状态获得一个新状态,

返回小括号大括号是一个对象




createStore接收reducer,不同状态有不同的函数(reducers)
引入reducer函数counter,store管理上reducer

一个模块只有一个default,不同type做的事情不一样swith代替if/else,return新的状态。
要在原来的基础上增加data值

不变化的常量值


状态state交给reducer管理,更新状态的操作setState交给reducer去做
把store传递给 App

Props中有store

不能有大括号,本来就是数值不能加大括号

更新状态的操作

传递表示不一样,要先得到count值


监听为了状态更新了需要重新绘制组件


reducers.js ,根据reducer产生新的state的函数
/*
根据老的state和指定action, 处理返回一个新的state
*/
import {INCREMENT, DECREMENT} from './action-types'
export function counter(state = 0, action) {
console.log('counter', state, action)
switch (action.type) {
case INCREMENT:
return state + action.number
case DECREMENT:
return state - action.number
default:
return state
}
}
/*
action creator模块
*/
import {INCREMENT, DECREMENT} from './action-types'
export const increment = number => ({type: INCREMENT, number})
export const decrement = number => ({type: DECREMENT, number})
/*
Action对象的type常量名称模块
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import App from './components/app'
import {counter} from './redux/reducers'
// 根据counter函数创建store对象
const store = createStore(counter)
// 定义渲染根组件标签的函数
const render = () => {
ReactDOM.render(
<App store={store}/>,
document.getElementById('root')
)
}
// 初始化渲染
render()
// 注册(订阅)监听, 一旦状态发生改变, 自动重新渲染
store.subscribe(render)
/*
应用组件
*/
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import * as actions from '../redux/actions'
export default class App extends Component {
static propTypes = {
store: PropTypes.object.isRequired,
}
increment = () => {
const number = this.refs.numSelect.value * 1
this.props.store.dispatch(actions.increment(number))
}
decrement = () => {
const number = this.refs.numSelect.value * 1
this.props.store.dispatch(actions.decrement(number))
}
incrementIfOdd = () => {
const number = this.refs.numSelect.value * 1
let count = this.props.store.getState()
if (count % 2 === 1) {
this.props.store.dispatch(actions.increment(number))
}
}
incrementAsync = () => {
const number = this.refs.numSelect.value * 1
setTimeout(() => {
this.props.store.dispatch(actions.increment(number))
}, 1000)
}
render() {
return (
<div>
<p>
click {this.props.store.getState()} times {' '}
</p>
<select ref="numSelect">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>{' '}
<button onClick={this.increment}>+</button>
{' '}
<button onClick={this.decrement}>-</button>
{' '}
<button onClick={this.incrementIfOdd}>increment if odd</button>
{' '}
<button onClick={this.incrementAsync}>increment async</button>
</div>
)
}
}
41_尚硅谷_redux_counter应用_react-redux版本
(redux_counter应用_react-redux版本&)
redux_counter应用_react-redux版本&
React-redux降低耦合度




Provider包裹起来,代替subscribe

propTypes声明接收的属性
![]()

不依赖




Props有count解构赋值,纯react的代码没有redux
![]()

通过connect连接react的组件和redux,将两个组件关联起来,最终方法与数据状态都要从redux取,

不能直接暴露App,connect执行要接收一个组件类,接收一个新的组件,返回一个新的组件不是原来的组件。
函数返回值类型是对象,第一个参数count在state中存的,第二个参数传入的是两个函数,对象中的所有数据都会解构交给App组件,做为属性传递。

取状态应该是props.count一点redux代码都没有

![]()

整个应用进行全局的管理,
![]()
内部包装了3个组件

第一个是一般属性,后面两个是函数属性

属性都在Redux中,声明接收属性与方法,其他的都是一般的写法。
Count要和App中声明的属性名一致


等价,方法increment要和声明的propTypes属性名一致,作为App属性的属性名传递,后面黑色的属性值increment要和actions声明的方法一致。


最后要调用dispatch,变化的actions后面的

UI组件不适用redux的API,容器组件使用Redux的API

App没有用到Redux组件

返回的组件利用Redux相关的API生成的

不需要redux的API



包装Counter,把状态count变为了属性没有redux前自己管理属性,用来redux后接受属性


引入containers下面的app.jsx

状态操作写常量,

生成action对象的函数,没有业务

根据原来老的状态和action形成一个新的状态

Store固定写法

Provider要接收store,connect将组建与redux连接起来,目的向UI组件传递mapStat两个属性,mapStateToprops参数类型是函数

函数返回是一个对象,从state中读数据,没法写对象回调函数传入state

mapDispatchToProps参数类型是对象,包含一些从actions模块中得到的方法,转换成对于dispatch函数调动。

reducers.js
import {combineReducers} from 'redux'
import {
INCREMENT,
DECREMENT
} from './action-types'
function counter(state = 0, action) {
console.log('counter', state, action)
switch (action.type) {
case INCREMENT:
return state + action.number
case DECREMENT:
return state - action.number
default:
return state
}
}
export default combineReducers({
counter
})
actions.js
/*
action creator模块
*/
import {
INCREMENT,
DECREMENT
} from './action-types'
export const increment = number => ({type: INCREMENT, number})
export const decrement = number => ({type: DECREMENT, number})
// 异步action creator(返回一个函数)
export const incrementAsync = number => {
return dispatch => {
setTimeout(() => {
dispatch(increment(number))
}, 1000)
}
}
counter.js
/*
包含Counter组件的容器组件
*/
import React from 'react'
import PropTypes from 'prop-types'
export default class Counter extends React.Component {
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired
}
increment = () => {
const number = this.refs.numSelect.value*1
this.props.increment(number)
}
decrement = () => {
const number = this.refs.numSelect.value*1
this.props.decrement(number)
}
incrementIfOdd = () => {
const number = this.refs.numSelect.value*1
let count = this.props.count
if(count%2===1) {
this.props.increment(number)
}
}
incrementAsync = () => {
const number = this.refs.numSelect.value*1
this.props.incrementAsync(number)
}
render () {
return (
<div>
<p>
click {this.props.count} times {' '}
</p>
<select ref="numSelect">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>{' '}
<button onClick={this.increment}>+</button>{' '}
<button onClick={this.decrement}>-</button>{' '}
<button onClick={this.incrementIfOdd}>increment if odd</button>{' '}
<button onClick={this.incrementAsync}>increment async</button>
</div>
)
}
}
store.js
import React from 'react'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducers'
// 根据counter函数创建store对象
export default createStore(
reducers,
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)
app.jsx
/*
包含Counter组件的容器组件
*/
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入action函数
import {increment, decrement} from '../redux/actions'
import Counter from '../components/counter'
// 向外暴露连接App组件的包装组件
export default connect(
state => ({count: state}),
{increment, decrement}
)(Counter)
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import App from './containers/app'
import store from './redux/store'
// 定义渲染根组件标签的函数
ReactDOM.render(
(
<Provider store={store}>
<App/>
</Provider>
),
document.getElementById('root')
)
42_尚硅谷_redux_counter应用_redux异步版本
(redux_counter应用_redux异步版本&)
redux_counter应用_redux异步版本&
发Ajax请求,redux不支持异步操作,express通过中间件实现功能扩展当前库的插件

![]()
应用异步中间件,

异步在组件中实现的

过了一秒增加,函数参数是从容器组件传过来的incrementAsync


定义异步actions返回是一个函数,setTimeout里面触发更新,调用dispatch分发actions才会产生新的状态。
外面是一个函数,return的还是一个函数,在函数中才能执行异步代码,分发同步action

用上中间件才支持return函数

43_尚硅谷_redux_counter应用_使用redux调试工具
(redux_counter应用_使用redux调试工具&)
redux_counter应用_使用redux调试工具&
状态不在组件上了,



再包裹一层扩展插件



44_尚硅谷_redux_comment应用_redux版本_同步功能
(redux_comment应用_redux版本_同步功能&)
redux_comment应用_redux版本_同步功能&
数据异步从后台获取,

发请求获取数据,列表1s后显示出来

UI组件和容器组件,ui组件需要redux的API包装


App.js中的comments属性获取,异步的操作要放到actions中去做,统一更新的函数应该是属性中传入的。以前自身的state和函数由外部的redux传入





组件要用connect包裹一下,才能接受三个属性

返回对象要有属性名,reducer中的state就是一个数组。

对于属性和数据的操作,加和减是同步操作,点击立即生效

同步增加的action操作和删除的action操作,调用函数有没有传递参数


方法名与connect引入的一致

删除从下标开始删除

操作评论数组,以操作的数据命名,state要指定默认初始值,switch写法比较固定,default是在第一次的时候调用

返回新的状态也是一个数组,不能直接修改state,action.data是comment对象,后面原理状态在老状态新增加data。Action.data是一个下标状态要变了,要返回一个数组filter不改变以前数组,新数组比以前的数组少一些元素。


向外暴露store对象,通过createStore方法产生的。
向外暴露comments,必须要加大括号没有加defualt可能暴露多个。


45_尚硅谷_redux_comment应用_redux版本_异步功能
(redux_comment应用_redux版本_异步功能&)
redux_comment应用_redux版本_异步功能&
一上来应该是一个空数组,actions中增加异步获取数据的action,dispatch写异步代码模拟发送ajax请求,分发一个同步的action接收数据,action不能自己创建需要同步函数创建



有了新的action type,reducer要处理,接收新的状态数组,新数组成为了新的状态state

Receive_comments操作什么时候触发,receviceComments不用暴露


![]()
getComments函数要传递个App,getComments操作上来就要做声明调用

声明更加完善,异步代码要封装到actions中

上传代码没有异步代码

Reducer中有多个函数,分别暴露不太好


就管理一个comments函数

管理多个函数,combineReducers合并多个放在一起管理

把所有函数的export去掉,统一暴露,combineReducers也是一个函数结果,两个方法名,最好同名,不同名冒号。State是一个对象里面有对应的2和[]数组对应返回状态的结果

暴露对应属性上

Default暴露不能写大括号得到某一个函数,直接引入reducers

Reducers只能暴露它

最后App要变化,要管理两个state状态,对象包装起来,真实应用有多个状态会有多个reducer

action-types.js
export const ADD_COMMENT = 'ADD_COMMENT'
export const DELETE_COMMENT = 'DELETE_COMMENT'
export const RECEIVE_COMMENTS = 'RECEIVE_COMMENTS'
actions.js
import {
ADD_COMMENT,
DELETE_COMMENT,
RECEIVE_COMMENTS
} from './action-types'
export const addComment = (comment) => ({type: ADD_COMMENT, data: comment})
export const deleteComment = (index) => ({type: DELETE_COMMENT, data: index})
const receiveComments = (comments) => ({type: RECEIVE_COMMENTS, data: comments})
export const getComments = () => {
return dispatch => {
setTimeout(() => {
const comments = [
{
username: "Tom",
content: "ReactJS好难啊!",
id: Date.now()
},
{
username: "JACK",
content: "ReactJS还不错!",
id: Date.now() + 1
}
]
dispatch(receiveComments(comments))
}, 1000)
}
}
reducers.js
import {combineReducers} from 'redux'
import {
ADD_COMMENT,
DELETE_COMMENT,
RECEIVE_COMMENTS
} from './action-types'
const initComments = []
function comments(state = initComments, action) {
switch (action.type) {
case ADD_COMMENT:
return [...state, action.data]
case DELETE_COMMENT:
return state.filter((c, index) => index !== action.data)
case RECEIVE_COMMENTS:
return action.data
default:
return state
}
}
export default combineReducers({
comments
})
store.js
import React from 'react'
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducers'
// 根据counter函数创建store对象
export default createStore(
reducers,
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'
import App from './components/app/app'
import store from './redux/store'
// 定义渲染根组件标签的函数
ReactDOM.render(
(
<Provider store={store}>
<App/>
</Provider>
),
document.getElementById('root')
)
app.jsx
import React from 'react'
import {connect} from 'react-redux'
import CommentAdd from '../comment-add/comment-add'
import CommentList from '../comment-list/comment-list'
import {getComments} from '../../redux/actions'
class App extends React.Component {
componentDidMount() {
//模拟异步获取数据
this.props.getComments()
}
render() {
return (
<div>
<header className="site-header jumbotron">
<div className="container">
<div className="row">
<div className="col-xs-12">
<h1>请发表对React的评论</h1>
</div>
</div>
</div>
</header>
<div className="container">
<CommentAdd/>
<CommentList/>
</div>
</div>
)
}
}
export default connect(
null,
{getComments}
)(App)
comment-list.jsx
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import CommentItem from '../comment-item/comment-item'
import './commentList.css'
class CommentList extends React.Component {
render () {
let comments = this.props.comments
let display = comments.length > 0 ? 'none' : 'block'
return (
<div className="col-md-8">
<h3 className="reply">评论回复:</h3>
<h2 style={{ display: display }}>暂无评论,点击左侧添加评论!!!</h2>
<ul className="list-group">
{
comments.map((comment, index) => {
console.log(comment)
return <CommentItem comment={comment} key={index} index={index}/>
})
}
</ul>
</div>
)
}
}
CommentList.propTypes = {
comments: PropTypes.array.isRequired,
}
export default connect(
state => ({comments: state.comments})
)(CommentList)
comment-add.jsx
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {addComment} from '../../redux/actions'
class CommentAdd extends React.Component {
constructor (props) {
super(props)
this.state = {
username: '',
content: ''
}
this.addComment = this.addComment.bind(this)
this.changeUsername = this.changeUsername.bind(this)
this.changeContent = this.changeContent.bind(this)
}
addComment () {
// 根据输入的数据创建评论对象
let { username, content } = this.state
let comment = { username, content }
// 添加到comments中, 更新state
this.props.addComment(comment)
// 清除输入的数据
this.setState({
username: '',
content: ''
})
}
changeUsername (event) {
this.setState({
username: event.target.value
})
}
changeContent (event) {
this.setState({
content: event.target.value
})
}
render () {
return (
<div className="col-md-4">
<form className="form-horizontal">
<div className="form-group">
<label>用户名</label>
<input type="text" className="form-control" placeholder="用户名"
value={this.state.username} onChange={this.changeUsername}/>
</div>
<div className="form-group">
<label>评论内容</label>
<textarea className="form-control" rows="6" placeholder="评论内容"
value={this.state.content} onChange={this.changeContent}></textarea>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-default pull-right" onClick={this.addComment}>提交</button>
</div>
</div>
</form>
</div>
)
}
}
CommentAdd.propTypes = {
addComment: PropTypes.func.isRequired
}
export default connect(
null,
{addComment}
)(CommentAdd)
comment-item.jsx
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import './commentItem.css'
import {deleteComment} from '../../redux/actions'
class CommentItem extends React.Component {
constructor(props) {
super(props)
}
deleteComment = () => {
let username = this.props.comment.username
if (window.confirm(`确定删除${username}的评论吗?`)) {
this.props.deleteComment(this.props.index)
}
}
render() {
let comment = this.props.comment
return (
<li className="list-group-item">
<div className="handle">
<a href="javascript:" onClick={this.deleteComment}>删除</a>
</div>
<p className="user"><span>{comment.username}</span><span>说:</span></p>
<p className="centence">{comment.content}</p>
</li>
)
}
}
CommentItem.propTypes = {
comment: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
deleteComment: PropTypes.func.isRequired
}
export default connect(
null,
{deleteComment}
)(CommentItem)
4067

被折叠的 条评论
为什么被折叠?



