react的使用
引入.js文件使用react
通过脚手架工具来编码
create-react-app
环境准备
安装好node环境 安装npm
查看安装版本
node -v
npm -v
安装create-react-app
npm install -g create-react-app
//换源
npm config set registry https://registry.npm.taobao.org
//配置后通过以下方法验证是否成功
npm config get registry
创建一个react-app
create-react-app todolist
工程目录文件简介
index.js是整个程序运行的入口文件
js中可以引入css文件的
import React from 'react';
import ReactDOM from 'react-dom';
// all in js 在js中引入css文件
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
简便工程代码
简化src的代码只留下,index.js,App.js
App.js
import React from "react";
function App() {
return (
<div>
hello react
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
react中的组件
这里App.js中的App就是一个组件
低版本react中的App.js
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 把app组件挂载到id为root的节点下
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
jsx语法
在js中写的html标签称为jsx语法
jsx语法中 如果要使用自己的组件 则用标签包起来,且组件必须以大写开头
react基础知识
使用react编写todolist功能
TodoList.js
import React from "react";
function TodoList() {
return (
<div>
<div>
<input/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>study react</li>
</ul>
</div>
);
}
export default TodoList;
jsx语法要求最外层需要包裹一层div
可以使用Fragment占位符替代,Fragment不会显示出来
import React, {Fragment} from "react";
function TodoList() {
return (
<Fragment>
<div>
<input/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>study react</li>
</ul>
</Fragment>
);
}
export default TodoList;
react中的响应式设计思想和事件绑定
操作数据而不是操作dom
TodoList.js
import React, {Fragment,Component} from "react";
class TodoList extends Component{
constructor(props) {
super(props);
this.state = {
inputValue:'',
list:[]
}
}
render() {
return (
<Fragment>
<div>
<input onChange={this.inputChange.bind(this)}
value = {this.state.inputValue}/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>study react</li>
</ul>
</Fragment>
);
}
inputChange(e) {
console.log(e.target.value)
// 设置state中的inputValue的值
this.setState({
inputValue:e.target.value
})
}
}
export default TodoList;
新增删除列表项
新增列表项
给button绑定事件,在原有数组基础上,将输入框中的值添加到数组中,然后遍历数组中的每一项的值,返回li标签
<input onChange={this.inputChange.bind(this)}
value = {this.state.inputValue}/>
<button onClick={this.btnClick.bind(this)}>提交</button>
</div>
btnClick() {
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
}
<ul>
{
this.state.list.map( (item,index) =>{
return <li key={index} onClick={this.btnDelete.bind(this,index)}>{item}</li>
})
}
</ul>
删除列表项
给li标签绑定btnDelete事件,并绑定数组下标index,传入下标,我们一般不去改变原有的state,而是单独拷贝一份出来进行修改
然后通过setState方法设置list的值
btnDelete(index) {
// 不去改变原有的state 而是相当于拷贝一份出来
const list = [...this.state.list]
list.splice(index,1)
this.setState({
list:list
})
}
jsx语法细节补充
小写字母开头标签,会作为普通标签。当遇到大写字母开头的标签会认为是组件
jsx中注释写法{/111/}
{
//注释
}
如果要是标签的内容不被转义,使用dangerouslySetInnerHTML
<li
key={index}
onClick={this.btnDelete.bind(this,index)}
// {}中一般是一个js表达式,{{__html:item}}表达式中又是一个对象
dangerouslySetInnerHTML={{__html:item}}
></li>
使用label能够起到扩大输入的范围
<div>
<label htmlFor="insertarea">输入内容</label>
<input
id="insertarea"
className='input'
onChange={this.inputChange.bind(this)}
value = {this.state.inputValue}/>
</div>
在js中引入index.css
.input {
border: 1px solid red;
}
import './index.css'
<input
id="insertarea"
className='input'
onChange={this.inputChange.bind(this)}
value = {this.state.inputValue}/>
<button onClick={this.btnClick.bind(this)}>提交</button>
</div>
拆分组件与组件之间的传值问题
父组件向子组件传递数据
新建TodoItem.js组件
import React,{Component} from 'react';
class TodoItem extends Component {
render() {
return <li>{this.props.content}</li>
}
}
export default TodoItem
修改TodoList.js组件
<ul>
{
this.state.list.map( (item,index) =>{
return (
<div>
<TodoItem content={item}/>
{/*
<li
key={index}
onClick={this.btnDelete.bind(this,index)}
dangerouslySetInnerHTML={{__html:item}}
></li>
*/}
</div>
)
})
}
</ul>
注意
父组件向子组件传递内容,通过属性的形式来传递content={item}
子组件通过this.props.content
来接收传递过来的content属性
子组件向父组件传递数据
那么子组件如何向父组件传递数据呢
我想点击删除数据,本质上是修改list,但是子组件是不允许直接修改父组件数据的
首先我们需要获取数组每一项的下标,通过父组件传递给子组件
<TodoItem content={item} index={index}/>
然后子组件如何调用父组件的btndelete方法去改变父组件里面的数据
父组件通过属性deleteItem 将方法btnDelete传递给子组件
<TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>
子组件中调用父组件中的数据以及方法
import React,{Component} from 'react';
class TodoItem extends Component {
constructor(props) {
super(props);
}
render() {
return <li onClick={this.handleClick.bind(this)}>{this.props.content}</li>
}
handleClick() {
this.props.deleteItem(this.props.index)
}
}
export default TodoItem
这里this.handleClick.bind(this)可以通过另一种方式绑定节省性能
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this)
}
原来的通过属性deleteItem传递给子组件的方法btnDelete,他的指向应当指向父组件TodoList
<TodoItem content={item} index={index} deleteItem = {this.btnDelete.bind(this)}/>
handleClick() {
this.props.deleteItem(this.props.index)
//这里相当于this.btnDelete() 这里的this要指向TodoList,而不是TodoItem
}
TodoList代码优化
对代码进行解构赋值
render() {
// 通过es6语法对代码进行解构赋值
const {content} = this.props
return <li onClick={this.handleClick}>{content}</li>
}
handleClick() {
const {deleteItem,index} = this.props
deleteItem(index)
}
将this指向在constructor中定义好
constructor(props) {
super(props);
this.state = {
inputValue:'',
list:[]
}
this.inputChange = this.inputChange.bind(this)
this.btnClick = this.btnClick.bind(this)
this.btnDelete = this.btnDelete.bind(this)
}
将逻辑代码通过方法进行封装
<ul>
{this.getItem()}
</ul>
getItem() {
return this.state.list.map( (item,index) =>{
return (
<div>
<TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>
</div>
)
})
}
setState数据 之前是传入对象
现在新版是传入函数,在函数中传入对象,从而setState变成了一个异步的setState
不能直接传入e,需要对其进行保存
inputChange(e) {
console.log(e.target.value)
const value = e.target.value
this.setState(() =>{
return {
inputValue: value
}
})
// this.setState({
// inputValue:e.target.value
// })
}
这里prevState是修改前的一次state
btnClick() {
this.setState((prevState) =>{
return {
list:[...prevState.list,prevState.inputValue],
inputValue:''
}
})
// this.setState({
// list:[...this.state.list,this.state.inputValue],
// inputValue:''
// })
}
btnDelete(index) {
// 不去改变原有的state 而是相当于拷贝一份出来
// const list = [...this.state.list]
// list.splice(index,1)
// this.setState({
// list:list
// })
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index,1)
return {
list
}
})
}
解决key的警告
有div则写在循环的最外层元素上
这里div是为了包裹之前的注释代码的 这里可以将div删除 将key写在TodoItem组件上
<TodoItem key={index} content={item} index={index} deleteItem = {this.btnDelete}/>
getItem() {
return this.state.list.map( (item,index) =>{
return (
<div key={index}>
<TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>
</div>
)
})
}
react的一些特性
直接操作dom 命令式编程
声明式开发 react
可以并存其它框架
只管<div id="root"></div>
的渲染
我们可以在Index中编写其它dom
组件化
单向数据流 父组件向子组件传值(list),子组件可以使用这个值,但是不能改变它
视图层框架
函数式编程
安装react开发调试工具
翻墙安装react-developer-tools
PropTypes与DefaultProps的应用
PropTypes
子组件接收父组件传值
对它传过来的数据的类型进行强校验
render() {
// 通过es6语法对代码进行解构赋值
const {content,test} = this.props
return <li onClick={this.handleClick}>
{test} - {content}
</li>
}
TodoItem.propTypes = {
test:PropTypes.string.isRequired,
// 要求content必须传递
// content:PropTypes.string.isRequired,
//content是一个number类型或者string类型
//content:PropTypes.arrayOf(PropTypes.string,PropTypes.number),
content:PropTypes.string,
deleteItem:PropTypes.func,
index:PropTypes.number
}
TodoItem.defaultProps = {
// 当test没有传值时 给它一个默认值
test:'hello'
}
Props State 与render函数
当组件的state或者props发生改变的时候,render函数就会重新执行
之所以数据发生变化,页面跟着变化,是因为页面是由render函数渲染出来的
state变化的时候,render函数就会重新执行一次 页面跟着变化
Test.js
1.另一方面当Props发生变化时render函数也会执行
2.当父组件的render函数被运行时,它的子组件的render都将被重新运行
import React,{Component} from 'react'
class Test extends Component {
render() {
// 当父组件的render函数被运行时,它的子组件的render都将被重新运行
console.log('test render')
return <div>{this.props.content}</div>
}
}
export default Test
React中的虚拟DOM
1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
4.state 发生改变
5.数据结合模板 生成真实的DOM 替换原始的DOM
缺点:
第一次生成了一个完整的Dom片段
第二次生成了一个完整的DOM片段
第二次的dom替换第一次的dom 非常耗性能
假如第二次只是改变模板中的部分代码,但是需要替换整个dom 影响性能
改良:
1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
4.state 发生改变
5.数据结合模板 生成真实的DOM 并不直接替换原始的DOM
6.新的dom和原始的dom做比对,找差异
7.找出input框中发生了变化
8.只用新的dom中的input元素 替换掉老的dom中的Input元素
缺点:
性能的提升并不明显
1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
<div id='abc'><span>hello world</span></div>
4.生成虚拟dom(虚拟dom就是一个js对象,用它来描述真实的dom) (js生成一个js对象损耗性能较低 而js创建dom 损耗性能较高)
['div',{id:'abc'},['span',{},'hello world']]
5.state 发生改变
6.生成新的虚拟dom 假设数据发生变化
['div',{id:'abc'},['span',{},'bye bye']]
7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容(提升性能)
8.直接操作DOM 改变span中的内容
深入理解虚拟DOM
然而实际上的过程是:
1.首先要有state 数据
2.然后jsx 模板
3.数据+模板生成虚拟dom(虚拟dom就是一个js对象,用它来描述真实的dom) (js生成一个js对象损耗性能较低 而js创建dom 损耗性能较高)
['div',{id:'abc'},['span',{},'hello world']]
4.用虚拟DOM结构 生成真实的DOM 来显示
<div id='abc'><span>hello world</span></div>
5.state 发生改变
6.生成新的虚拟dom 假设数据发生变化
['div',{id:'abc'},['span',{},'bye bye']]
7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容(提升性能)
8.直接操作DOM 改变span中的内容
jsx->createElement->js对象(虚拟dom)->真实的dom
render() {
// 通过es6语法对代码进行解构赋值
const {content,test} = this.props
return <div>item</div>
}
这里的模板可以用
React.createElement('div',{},'item')替代
虚拟dom的好处
1.性能提升
2.跨端应用得以实现 React Native
虚拟DOM的Diff算法
比较原始虚拟DOM和新的虚拟DOM的区别
底层可以吧多次setState结合成一次setState
归根结底是setState发生变化时 ,并且setState是异步方法 提高了底层的性能
比对是同层比对(虚拟比对速度快)
如果第一层有变化
则对下面的都进行替换
列表循环引入key 是为了提高虚拟dom比对的性能
之所以不建议key值使用index是因为
是因为没有办法保证原始的虚拟DOM上的key值和新的虚拟DOM上的key值一致
a 0 b 1 c 2
删除a,下标变化
b 0 c 1
用item作为key值
a a b b c c
删除a
b b c c
React中的ref使用
之前代码中content:PropTypes.arrayOf(PropTypes.string,PropTypes.number)
arrayOf指的是content是个数组 数组的内容可以是string类型或者是number类型
这里应该使用oneOfType 指以下类型中的一种
content:PropTypes.oneOfType([PropTypes.string,PropTypes.number])
inputChange(e) {
console.log(e.target)
const value = e.target.value
this.setState(() =>{
return {
inputValue: value
}
})
这里的e.target既是input dom元素
可以通过ref获取
<input
id="insertarea"
className='input'
onChange={this.inputChange}
value = {this.state.inputValue}
ref={(input) =>{this.ref=input}}
/>
创建一个ref引用,this.ref指向input dom节点
直接操作dom时会遇到的问题
<ul ref={(ul) =>{this.ul = ul}}>
{this.getItem()}
</ul>
btnClick() {
this.setState((prevState) =>{
return {
list:[...prevState.list,prevState.inputValue],
inputValue:''
}
},() => {
console.log(this.ul.querySelectorAll('ul').length)
})
//不能在这里直接输出 因为setState是异步函数
//可能执行的先后顺序就会变化
//console.log(this.ul.querySelectorAll('ul').length
}
正确的做法是在setState传入第二个参数,传入一个回调函数,这个回调函数会等setState异步执行完成,再进行回调
React中的生命周期函数
生命周期函数指在某一时刻组件会自动调用执行的函数
组件挂载渲染
//已经不推荐使用 在组件即将被挂载到页面的时候自动执行
componentWillMount () {
console.log('UNSAFE component will mount')
}
//组件被挂载到页面之后 自动执行
componentDidMount() {
console.log(' component did mount')
}
只会第一次挂载的时候执行
之后不执行
—————————————————
组件更新过程
props或者states发生变化
//数据被更新之前自动执行
shouldComponentUpdate(nextProps, nextState, nextContext) {
console.log('should component update')
// 如果 return false 那么数据不会被更新
return true;
}
//已经弃用 组件被更新之前 它会自动执行 但是它在shouldComponentUpdate之后被执行
//shouldComponentUpdate返回true才执行
componentWillUpdate(nextProps, nextState, nextContext) {
console.log('component will update')
}
//组件更新完成之后 他会被执行
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate')
}
//已经弃用
// 当一个组件从父组件接受了参数
//只要父组件的render函数被重新执行了 子组件的这个生命周期函数就会被执行
componentWillReceiveProps(nextProps, nextContext) {
console.log('child componentWillReceiveProps')
}
//当这个子组件即将被从页面剔除的时候 会被执行
componentWillUnmount() {
console.log('child componentWillUnmount')
}
生命周期函数是针对组件的
每一个组件都有这些生命周期函数
生命周期函数使用场景
render生命周期函数必须要有
React.Component内置了其他的所有生命周期函数
没有内置render生命周期函数
父组件发生改变,render函数重新渲染,子组件的render函数也会重新渲染
然而子组件的props并没有改变,所以子组件没有必要重新渲染
会带来性能上的消耗
在子组件中加入shouldComponentUpdate
当子组件的props改变时 才重新渲染
shouldComponentUpdate(nextProps, nextState, nextContext) {
if(nextProps != this.props.content){
return true
}
return false;
}
在react中发送ajax请求获取数据
yarn add axios
//ajax请求放在componentDidMount中
componentDidMount() {
axios.get('/api/tolist')
.then(()=>{alert('success')})
.catch(()=>{alert('error')})
}
使用Charles进行接口数据模拟
//ajax请求放在componentDidMount中
componentDidMount() {
axios.get('/api/todolist')
.then((res)=>{
alert('success')
this.setState(()=>{
return{
list:[...res.data]
}
})
})
.catch(()=>{alert('error')})
}
React实现css过渡动画
import React,{Component,Fragment} from "react";
import './style.css'
class App extends Component{
constructor(props) {
super(props);
this.state={
show:true
}
this.handleClick = this.handleClick.bind(this)
}
render() {
return (
<Fragment>
<div className={this.state.show ?'show':'hide'}>hello</div>
<button onClick={this.handleClick}>toggle</button>
</Fragment>
)
}
handleClick(){
this.setState({
show:this.state.show ? false : true
})
}
}
export default App;
.show {
opacity: 1;
transition: all 1s ease-in;
}
.hide {
opacity: 0;
transition: all 1s ease-in;
}
React中使用css的动画效果
.show {
animation: show-item 2s ease-in forwards;
}
.hide {
animation: hide-item 2s ease-in forwards;
}
@keyframes show-item {
0% {
opacity: 0;
color: red;
}
50% {
opacity: 0.5;
color: green;
}
100% {
opacity: 1;
color: blue;
}
}
@keyframes hide-item {
0% {
opacity: 1;
color: red;
}
50% {
opacity: 0.5;
color: green;
}
100% {
opacity: 0;
color: blue;
}
}
使用react-transition-group实现动画
yarn add react-transition-group
<Fragment>
<CSSTransition
in={this.state.show}
timeout={1000}
classNames='fade'
unmountOnExit
onEnter={(el) =>{el.style.color = 'blue'}}
appear={true}
>
<div>hello</div>
</CSSTransition>
<button onClick={this.handleClick}>toggle</button>
</Fragment>
onEnter动画第一帧的时候div这个元素的color添加蓝色
这里 unmountOnExit是隐藏元素时 会移除dom元素
appear是第一次刷新显示动画效果配合.fade-appear,.fade-appear-active
.fade-enter,.fade-appear {
opacity: 0;
}
.fade-enter-active,.fade-appear-active {
opacity: 1;
transition: opacity 1s ease-in;
}
.fade-enter-done {
opacity: 1;
/*color: red;*/
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 1s ease-in;
}
.fade-exit-done {
opacity: 0;
}
多个元素动画切换
import TransitionGroup from "react-transition-group/cjs/TransitionGroup";
<Fragment>
<TransitionGroup>
{
this.state.list.map((item) =>{
return (
<CSSTransition
timeout={1000}
classNames='fade'
unmountOnExit
onEnter={(el) =>{el.style.color = 'blue'}}
appear={true}
key={item}
>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.handleAddItem}>toggle</button>
</Fragment>
Redux
数据放到store中,方便了组件之间值的传递
Redux = Reducer + Flux
Redux工作流程
React Component 组件 相当于借书的人
Action Creators “你要借什么书这句话”
Store:管理员
Reducer:记录本
使用antd编写TodoList页面
yarn add antd
import React,{Component} from 'react'
import {Input,Button,List } from "antd";
import "antd/dist/antd.css"
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
render() {
return(
<div style={{marginLeft:'20px',marginTop:'20px'}}>
<div>
<Input placeholder='antd design' style={{width:'300px',marginRight:'20px'}}/>
<Button type="primary">提交</Button>
</div>
<List
style={{width:'300px',marginTop:'20px'}}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
)
}
}
export default TodoList
创建redux中的store
yarn add redux
在创建“管理员”state之前,得先告诉他管理书籍的记事本“reducer”
创建Reducer.js
const defaultState = {
inputValue:'aaa',
list:[1,2]
}
export default (state = defaultState,action) =>{
//state 存储的数据 默认数据都不存储
return state
}
创建State.js
import {createStore} from 'redux';
import reducer from "./reducer";
//可以知道reducer中的数据了 把记事本reducer传给管理员State
const store = createStore(reducer);
export default store;
TodoList.js
使用store.getState()来获取state
import store from "./store";
constructor(props) {
super(props);
this.state = store.getState();
}
<div style={{marginLeft:'20px',marginTop:'20px'}}>
<div>
<Input value={this.state.inputValue} placeholder='antd design' style={{width:'300px',marginRight:'20px'}}/>
<Button type="primary">提交</Button>
</div>
<List
style={{width:'300px',marginTop:'20px'}}
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
Action和Reducer的编写
安装插件 Redux DevTools
赋值 浅拷贝 深拷贝 理解 https://www.cnblogs.com/chengxs/p/10788442.html
store.subscribe(this.handleChangeStore)
<Input
value={this.state.inputValue}
placeholder='antd design'
style={{width:'300px',marginRight:'20px'}}
onChange={this.handleChange}
/>
<Button type="primary" onClick={this.handleClick}>提交</Button>
handleChange(e){
const action = {
type:'change_input_value',
value:e.target.value
}
store.dispatch(action)
}
handleChangeStore(){
this.setState(store.getState())
}
handleClick(){
const action = {
type:'add_list_item'
}
store.dispatch(action)
}
const defaultState = {
inputValue:'aaa',
list:[1,2]
}
//reducer可以接收state 但是不可以修改state
export default (state = defaultState,action) =>{
//之前的数据 与 当前的这句话的内容
if(action.type === 'change_input_value'){
//对state进行深拷贝
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
if(action.type ==='add_list_item'){
const newState = JSON.parse(JSON.stringify(state))
newState.list.push(newState.inputValue)
newState.inputValue=''
console.log(newState)
return newState
}
console.log(state)
return state
}
使用Redux完成TodoList的删除功能
renderItem={
(item,index) => <List.Item onClick={this.deleteListItem.bind(this,index)}>{item}</List.Item>}
deleteListItem(index){
console.log(index)
const action = {
type: 'delete_list_item',
index:index
}
store.dispatch(action)
}
if(action.type === 'delete_list_item'){
const newState = JSON.parse(JSON.stringify(state))
newState.list.splice(action.index,1)
return newState
}
ActionTypes的拆分
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_LIST_ITEM = 'add_list_item';
export const DELETE_LIST_ITEM = 'delete_list_item';
将TodoList.js和reducer.js中的变量进行替换
变量常量在代码中写错会报出异常 字符串不会报出异常
使用actionCreator统一创建action
将action抽离出来封装 提高代码可维护性
import {CHANGE_INPUT_VALUE,ADD_LIST_ITEM,DELETE_LIST_ITEM} from './actionTypes'
export const changeInpuValueAction = (value) => (
{
type:CHANGE_INPUT_VALUE,
value
}
)
export const addListItemAaction = function () {
return {
type:ADD_LIST_ITEM
}
}
export const deleteListItemAction = function (index){
return {
type:DELETE_LIST_ITEM,
index
}
}
handleChange(e){
const action = changeInpuValueAction(e.target.value)
store.dispatch(action)
}
handleClick(){
const action = addListItemAaction()
store.dispatch(action)
}
deleteListItem(index){
console.log(index)
const action = deleteListItemAction(index)
store.dispatch(action)
}
Redux设计和使用的三项原则
- store必须是唯一的
- 只有store能够改变自己的内容
- Reducer必须是纯函数
纯函数指的是 给定固定的输入 就一定有固定的输出 而且不会有副作用
核心api
createStore 创建store
store.dispatch 派发action
store.getState 获取store中的内容
store.subscribe 订阅store的改变
UI组件与容器组件的拆分
渲染 逻辑拆分
创建TodoListUI.js组件(UI组件)
import React,{Component} from 'react'
import {Button, Input, List} from "antd";
class TodoListUI extends Component{
render() {
return(
<div style={{marginLeft:'20px',marginTop:'20px'}}>
<div>
<Input
value={this.props.value}
placeholder='antd design'
style={{width:'300px',marginRight:'20px'}}
onChange={this.props.handleChange}
/>
<Button type="primary" onClick={this.props.handleClick}>提交</Button>
</div>
<List
style={{width:'300px',marginTop:'20px'}}
bordered
dataSource={this.props.list}
renderItem={
(item,index) => <List.Item onClick={(index) =>{this.props.deleteListItem(index)}}>{item}</List.Item>
}
/>
</div>
)
}
}
export default TodoListUI
TodoList.js修改(容器组件)
render() {
return (
<TodoListUI
value={this.state.inputValue}
handleChange={this.handleChange}
handleClick={this.handleClick}
deleteListItem={this.deleteListItem}
list={this.state.list}
/>
)
}
组件之间的传值通过props传递属性
无状态组件
性能比较高
const TodoListUI = (props) =>{
return(
<div style={{marginLeft:'20px',marginTop:'20px'}}>
<div>
<Input
value={props.value}
placeholder='antd design'
style={{width:'300px',marginRight:'20px'}}
onChange={props.handleChange}
/>
<Button type="primary" onClick={props.handleClick}>提交</Button>
</div>
<List
style={{width:'300px',marginTop:'20px'}}
bordered
dataSource={props.list}
renderItem={
(item,index) => <List.Item onClick={(index) =>{props.deleteListItem(index)}}>{item}</List.Item>
}
/>
</div>
)
}
Redux中发送异步请求获取数据
axios请求放在componentDidMount钩子函数中
componentDidMount() {
axios.get(
'/todolist.json'
).then((res)=>{
const data =res.data
const action = initListAction(data)
store.dispatch(action)
console.log(action)
})
}
配置一下跨域请求json 也可以放在本地public静态资源中
```javascript
"proxy": "http://139.XXX.XXX.xxx:8000"
创建action
export const initListAction = function (data){
return {
type:INIT_LIST_ITEM,
data
}
}
传给reducer
if(action.type === INIT_LIST_ITEM){
const newState = JSON.parse(JSON.stringify(state))
newState.list=action.data
return newState
}
使用Redux-thunk中间件实现ajax数据请求
Redux-thunk可以帮助我们将异步请求或者比较复杂的请求放到action中进行处理
安装 yarn add redux-thunk
componentDidMount() {
const action = initTodoList()
//这里执行store.dispatch(action) action是个函数
store.dispatch(action)
// axios.get(
// '/todolist.json'
// ).then((res)=>{
//
// const data =res.data
// const action = initListAction(data)
// store.dispatch(action)
// console.log(action)
// })
}
export const initTodoList = function () {
//对应 //这里执行store.dispatch(action) action是个函数
return (dispatch) =>{
axios.get(
'/todolist.json'
).then((res)=>{
const data =res.data
console.log(data)
//走redux流程
const action = initListAction(data)
//这里的action是对象
dispatch(action)
})
}
}
到底什么是Redux中间件
指的是action与store之间
其实实质是对dispatch的封装
传给dispatch (如果参数是对象直接传给store,如果参数是函数,则将函数执行 )
Redux-saga中间件使用
import createSagaMiddleware from 'redux-saga'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
// applyMiddleware(thunk),
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mySaga)
创建sagas.js
import { put, takeEvery } from 'redux-saga/effects'
import {GET_INIT_LIST} from './actionTypes'
import {initListAction} from './actionCreators'
import axios from "axios";
import store from "./index";
function* getInitList() {
// axios.get(
// '/todolist.json'
// ).then((res)=>{
//
// const data =res.data
// const action = initListAction(data)
// store.dispatch(action)
// console.log(action)
// })
//generator函数中可以这样写
//yield 等axios请求之后
const res = yield axios.get('/todolist.json')
const action = initListAction(res.data)
// 等action处理完成之后
yield put(action)
}
// es6 generator函数
function* mySaga() {
// 只要接收到GET_INIT_LIST类型的action的时候 就会执行getInitList方法
yield takeEvery(GET_INIT_LIST, getInitList);
}
export default mySaga;
export const getTodoList = function () {
return {
type:GET_INIT_LIST
}
}