react学习
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render( ,document.getElementById('root'));
ReactDOM的render函数可以把App组件挂载到id为root的节点下。
注意一定要引入react包, 是一个jsx语法,需要依赖react包去进行编译。
jsx语法
jsx中如果要使用自己创建的组件,组件一定要以大写开头
想要在jsx中访问state中的数据或者写表达式都要用{}
响应式设计思想和事件绑定
- 所有的数据在state(状态)中定义
- 想要再jsx中访问state的数据,需要用{}包括
- 原生绑定事件比如onchange在jsx中要使用驼峰onChange
- 绑定事件的时候注意用bind改变this指向。
- 想要改变state中的数据,需要使用setState函数。
- 遍历
-
{
this.state.list.map((item,index)=>{
return
- {item} }) }
- immutable(不可变的) 禁止去修改state中的数据
handleItemDelete(index) {
//禁止使用
this.state.list.splice(index,1);
//推荐使用
const list = [...this.state.list];
list.splice(index, 1);
this.setState({
list,
});
}
jsx语法细节补充
- 注释
- {//单行注释}
- {/多行注释/}
- css类型用className替换class
- 不转义输出
{this.state.list.map((item, index) => {
return (
);
})}
- label和for
- for会和循环中的for产生歧义,把for换成htmlFor
父子组件通信
- 父组件通过属性可以传递给子组件数据或者函数
- 子组件通过props可以获取父组件传递过来的内容,可以通过父组件传的函数调用这个函数传值给父组件。
- 代码优化点:
- 可以用结构赋值的方式解构出props中的数据和函数
- this指向的绑定统一放在constructor中
- 循环遍历可以封装成一个函数
- {this.getDoToList()}
); }); } - 新版本的react改变值setState接收一个函数,该函数返回一个对象(这个对象就是新的state的值)。接收的函数中有一个形参prevState,就是state中的数据。下面是代码优化
handleInputChange(e) { // this.setState({ // inputVal: e.target.value, // }); //setState中的函数是异步执行的(为了提升性能) const value = e.target.value; this.setState(()=>({ inputVal:value })); } handleBtnClick(){ // const list = [...this.state.list]; // list.push(this.state.inputVal); // this.setState({ // list, // inputVal: "", // }); //prevState === this.state this.setState((prevState)=>({ list:[...prevState.list,prevState.inputVal], inputVal:"" })) } handleItemDelete(index) { // const list = [...this.state.list]; // list.splice(index, 1); this.setState((prevState)=>{ const list = [...this.state.list]; list.splice(index,1); return { list } }); }
react衍生出的思考
- 声明式开发
- 与其他框架并存
- 组件化
- 单向数据流
- 子组件只接收父组件传过来的数据,不能对数据进行修改。
- 父组件todoList想要删除数据,可以传递方法给子组件todoItem,子组件调用父组件传过来的方法删除,相当于还是父组件在删除数据。
- 视图层框架
- 函数式编程
- 对自动化测试支持比较好,自动化测试只要给函数一个数值,看结果是否符合预期就可以了。
state、props和render之间的关系
- 当state或者props中的数据改变的时候,自己的render函数会被执行。
- 当父组件的render函数执行的时候,里面用到的子组件的render函数也会被执行
了解虚拟DOM
- 方案一:假如没有react的实现思路
- 1.state数据
- 2.jsx模板
- 3.数据+模板=>生成真实DOM来显示。
- 4.state数据变化
- 5.数据+模板=>生成真实DOM,替换原来的DOM
- 缺陷:
- 第一次生成了完整的DOM片段
- 第二次生成了完整的DOM片段
- 第二次的DOM替换第一次的DOM,非常耗费性能。
- 方案二
- 1.state数据
- 2.jsx模板
- 3.数据+模板=>生成真实DOM来显示。
- 4.state数据变化
- 5.数据+模板=>生成真实DOM,并不直接替换原来的DOM
- 6.新的DOM(DocumentFragument)和原始的DOM作比对,找差异【损耗性能】
- 7.找出input框发生了变化(例子)
- 8.只用新DOM中的input元素,替换掉原始DOM的input元素。
- 缺陷:性能提升并不明显。
- 方案三
- 1.state数据
- 2.jsx模板
- 3.数据+模板=>生成虚拟DOM(虚拟DOM就是一个js对象,用它来描述真实DOM)
["div",{id:"box"},["span",{},"hello world"]]
- 4.用虚拟DOM来生成真实DOM并显示。
hello world- 5.state数据发生变化
- 6.数据+模板=>生成新的虚拟DOM
["div",{id:"box"},["span",{},"bye bye"]]
- 7.比较原始虚拟DOM和新的虚拟DOM中的区别,找到span中的内容有变化。【损耗性能】,损耗较小,和方案二生成真实dom相比损耗小得多。比对js对象和比对真实dom相比也是极大地提升了性能。
- 8.直接操作DOM,改变span中的内容。
深入了解虚拟DOM
- jsx => createElement => 虚拟DOM(js对象) => 真实DOM
render(){
return item
}
//等价于下面
render(){
return react.createElement("div",{className:"list-item"},"item")
}
render(){
return item
}
//等价于下面
render(){
return React.createElement("div",{},React.createElement("span",{},"item"));
}
- 虚拟DOM优点
- 1.性能得到了极大提升
- 2.使得跨端应用得以实现(虚拟DOM可以转换成真实DOM,也可以转换成原生应用的组件)。比如React Native
react中的diff算法
- setState是异步的,假如短时间内调用了3次setState,react就会把多次setState合并成为一个setState,就会省去额外的2次生成虚拟DOM以及虚拟DOM的比对。
- 同层比对算法:有两个虚拟DOM树,先从第一层(最顶层)比对,第一层不相同直接整个替换;第一层相同,对比第二层,一次类推。
- 如果每个虚拟dom都有一个key值,比对就可以大大提升性能。所以循环的时候要加上key,且要保障key唯一不重复。
//假如说用index作为key值
//旧的dom结构
a 0
b 1
c 2
//删除a节点 新的dom结构
b 0
c 1
//此时key就乱了 0和1所代表的的结构和旧的dom结构不一样了
//假如直接用内容本身(假设内容不重复)作为key值
//旧的dom结构
a a
b b
c c
//删除a 新的dom结构
b b
c c
//此时b和c代表的dom结构和旧的dom结构是一样的,大大提升了比对的性能。
ref
- {this.input=input}} />
- ref等于,里面写一个回调函数,回调函数的形参就是真实的dom节点,把它挂在到this上就可以访问了。
- setState是一个异步函数,如果想要在操作数据后得到最新的dom,可以再传一个回调函数。
this.setState((prevState)=>{},()=>{
//第二个回调函数会在更新完真实dom之后被触发。
})
生命周期函数
- 在某一时刻组件能够自动调用的函数。
- react的生命周期主要分为四个大的阶段:
- Initialization
- Initialization 初始化state和props
- Mounting
- componentWillMount => render => componentDidMount
- componentWillMount 在组件即将被挂载到页面的时候执行
- render 去做页面的挂载
- componentDidMount 组件被挂载到页面后执行
//注意:componentWillMount 和 componentDidMount只会在页面将要被挂载和页面已经挂在的时候执行一次,后续改变state数据,不会再次出发,而render会被出发多次。
- Updation
- 当props和state数据变化的时候,会触发一系列的生命周期函数
//1.shouldComponentUpdate 组件是否需要被更新,返回一个bool类型的值
//当父组件render执行的时候,子组件的render也会被执行,但有时候子组件并没有变化,不需要执行,所以可以通过shouldComponentUpdate控制
shouldComponentUpdate(nextProps,nextState){
if(nextProps.content!==this.props.content){
return true;
}else{
return false
}
}
//2.如果组件需要被更新会依次执行componentWillUpdate=>render=>componentDidUpdate
//3.componentWillReceiveProps是props改变的时候子组件才有可能执行,满足以下条件才会被执行
/*
1)接收了父组件传过来的数据
2)父组件的render函数触发了
a.如果子组件是第一次存在于父组件中,则不会执行
b.如果子组件不是第一次存在于父组件中,则会执行
*/
- Unmounting
- componentWillUnmount 组件即将被从页面移除的时候会被执行
发送ajax请求的时机
- componentWillMount
- 只是react的话没有问题,但是写reactNactive和用react写服务器端的重构时会有一些冲突,为了避免这种情况,推荐使用componentDidMount
- componentDidMount 【推荐】
redux
-
Redux = Reducer + Flux
- redux的设计原则
- store是唯一的
- 只有store能改变自己的内容
- reducer必须是纯函数
- 纯函数特点:给固定的输入(传参),就会有固定的输出(返回值),而且不会有副作用(不改变函数外部定义的任何值)。
- 函数中有异步操作,依赖时间的返回值,都不是一个纯函数
- 流程
- 组件中使用dispatch传入action告诉store要进行的操作,store通过reducer(是一个函数)完成相应的操作,返回一个新的对象newStore,store再把自身的数据替换成newStore的,组件就可以拿到store中的新数据了。
- 注意:
- reducer不能直接改变store中的数据,通过返回一个新对象告诉store最新的结果。
- store的几个常用api
- createStore
- store.dispatch
- store.getState
- store.subscribe
- 比较规范的代码如下
- 创建actionTypes.js来定义action中的type行为(定义为常量在其他地方引用规避出现拼写错误)
export const CHANGE_INPUT_VAL = "change_input_val";
export const ADD_TODO_ITEM = "add_todo_item";
export const DEL_TODO_ITEM = "del_todo_item";
- reducer.js 暴露一个函数,该函数接收当前state的值以及action并返回新的store的值
const defaultState = {
inputVal:"",
list:["春花秋月何时了","往事知多少"]
}
import { CHANGE_INPUT_VAL,ADD_TODO_ITEM,DEL_TODO_ITEM } from "./actionTypes";
//reducer是一个函数,返回一个新对象;不能直接修改state中的值
export default (state=defaultState,action)=>{
if(action.type==CHANGE_INPUT_VAL){
const newState = JSON.parse(JSON.stringify(state));
newState.inputVal = action.value;
return newState;
}else if(action.type==ADD_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(state.inputVal);
newState.inputVal = "";
return newState;
}else if(action.type==DEL_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index,1);
return newState;
}
return state;
}
- 创建actionCreators.js来定义action,方便其他地方引入
import { ADD_TODO_ITEM,DEL_TODO_ITEM,CHANGE_INPUT_VAL } from "./actionTypes";
export const getAddTodoItemAction = ()=>({
type:ADD_TODO_ITEM
})
export const getDelTodoItemAction = (index)=>({
type:DEL_TODO_ITEM,
index
})
export const getChangeInputValAction = (value)=>({
type:CHANGE_INPUT_VAL,
value
})
- todoList.js 组件业务代码
import { getAddTodoItemAction,getChangeInputValAction,getDelTodoItemAction } from "./store/actionCreators";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.addTodoItem = this.addTodoItem.bind(this);
this.changeInputVal = this.changeInputVal.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
}
componentDidMount(){
/*订阅store变化 改变state中的值*/
store.subscribe(this.handleStoreChange);
}
render(){
return (
(
{item}
)}
/>
)
}
addTodoItem() {
const action = getAddTodoItemAction();
store.dispatch(action);
}
delTodoItem(index){
const action = getDelTodoItemAction(index);
store.dispatch(action);
}
changeInputVal(e) {
const action = getChangeInputValAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(store.getState())
}
}
ui组件和容器组件
ui组件负责页面内容渲染(傻瓜组件:专注渲染,不问操作)
容器组件负责控制逻辑(聪明组件:专注控制)
优化代码如下:
class TodoListUI extends Component {
render() {
return (
(
//注意 此处想要调用父函数还传参数,可以在函数中调用父传过来的函数
{this.props.delTodoItem(index)}}>
{item}
)}
/>
);
}
}
export default TodoListUI;
- 当一个ui组件里面没有任何逻辑,只有一个render函数的时候,可以将其改变为一个无状态组件来提高性能。
- 二次优化如下
const TodoListUI = (props)=>{
return (
//此处是jsx代码
//之前写的this.props就可以换成只写props。
//普通组件是一个类,生成的对象有生命周期函数,还有render等;无状态组件就是一个函数,用来返回一段jsx,所以性能更快。
)
}
在redux中使用发送异步请求获取数据
- 开发工具redux-devtools和中间件redux-thunk结合使用参照相关连接
- redux-thunk
- 开发工具redux-devtools
//可以在生命周期componentDidMount中发送异步ajax请求,然后改变store中的数据,最后更新state中的数据
componentDidMount() {
/*订阅store变化*/
store.subscribe(this.handleStoreChange);
axios.get("/data/list.json").then(({data}) => {
const action = getInitListAction(data)
store.dispatch(action)
this.setState(store.getState())
});
}
redux-thank中间件(redux的中间件)实现ajax数据请求
- actionCreators.js
export const getInitListAction = (list)=>({
type:INIT_LIST,
list
})
//action返回一个函数,配合redux-thunk使用。
export const getTodoList = ()=>{
return (dispatch)=>{
axios.get("/data/list.json").then(({data})=>{
const action = getInitListAction(data);
dispatch(action)
})
}
}
- TodoList.js
import {getTodoList} from "actionCreators";
componentDidMount(){
const action = getTodoList();
//调用store的dispatch方法,dispatch方法会自行判断传进来的action的类型,如果传入的action是一个对象,就会把对象传给store;如果是函数就会执行这个函数(配合中间件redux-thunk才能实现这个效果),函数实参是store的dispatch方法,再用dispatch派发action给store。
store.dispatch(action);
}
redux中间件是什么
- redux-thunk中间件(中间指的是action和store的中间)就是对dispatch函数的一个升级封装;之前store.dispatch函数只接收一个action对象,做一些同步修改数据的操作;有了中间件,可以接收一个函数,我们可以在函数中做一些异步的操作。用中间件有助于我们解决异步代码的问题,也便于自动化测试。
redux-saga入门
- redux-saga也是redux的中间件,一般情况下redux-thunk已经能够满足我们的业务需求,如果复杂度特别高,我们也可以采用redux-saga;redux-saga会把异步请求数据的操作单独写在自定义js文件中来维护。
- 基础使用
- 1.在创建store的时候配置redux-saga中间件
//创建sagaMiddleware中间件对象
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware)
);
//mount it on the store
const store = createStore(reducer, enhancer);
//then run the saga
sagaMiddleware.run(mySaga)
export default store;
- 2.第一步中用到了自定义的saga文件
- 注意saga文件中暴露出去的函数和异步请求数据的函数都是generator函数。
import { put, takeEvery } from "redux-saga/effects";
import { getInitListAction } from "./actionCreators";
import { SET_LIST } from "./actionTypes"
import axios from "axios";
function* fetchTodoList() {
try {
const rst = yield axios.get("/data/list.json");
const action = getInitListAction(rst.data);
yield put(action);
} catch (e) {
//错误的处理
}
}
function* mySaga() {
//能够捕获到INIT_LIST action的操作,去执行fetchTodoList
yield takeEvery(SET_LIST, fetchTodoList);
}
export default mySaga;
- 3.todiList
import {
getSetListAction,
} from "./store/actionCreators";
componentDidMount() {
/*订阅store变化*/
store.subscribe(this.handleStoreChange);
//使用redux-saga
const action = getSetListAction()
store.dispatch(action)
}
react-redux终章
- 1.局部安装相关包redux和react-redux
- 2.使用Provider组件给全局的根组件提供store数据
//程序入口文件index.js代码
import {Provider} from "react-redux";
import TodoList from "./TodoList";
ReactDOM.render(
,
document.getElementById('root')
);
- 在要使用store中数据的组建中,把当前组件和sore连接起来
//Todolist.js代码
import {Component} from "react";
import {connect} from "react-redux";
class TodoList extends Component{
constructor(props){
super(props);
}
render(){
return (
- dell
)
}
}
☆☆☆☆☆☆☆☆接着上面的内容☆☆☆☆☆☆☆
//把store中的数据映射到props中,返回的对象就是映射到props中的值
const mapStateToProps = (state)=>{
return {
inputVal:state.inputVal
}
}
//把store的dispatch映射到props中,返回的对象就是映射到props中的值
const mapDispatchToProps = (dispatch)=>{
return {
changeInputVal(e){
const action = {
type:"change_input_val",
value:e.target.value
}
dispatch(action)
}
}
}
//让TodoList与store做连接,如何做连接呢,有个映射关系如下:把mapStateToProps和mapDispatchToProps都映射到props中
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);
加入actionTypes和actionCreator,修改后代码如下
import { Component } from "react";
import { connect } from "react-redux";
import { addItem, changeInputVal,delItem } from "./store/actionCreator.js";
class TodoList extends Component {
constructor(props) {
super(props);
}
render() {
const { changeInputVal, add, list, del } = this.props;
return (
{
list.map((item,index)=>{
return - {del(index)}} key={index}>{item}
})
}
);
}
}
☆☆☆☆☆☆☆☆接着上面的内容☆☆☆☆☆☆☆
//把store中的数据映射到props中,返回的对象就是映射到props中的值
const mapStateToProps = (state) => {
return {
inputVal: state.inputVal,
list: state.list,
};
};
//把store的dispatch映射到props中,返回的对象就是映射到props中的值
const mapDispatchToProps = (dispatch) => {
return {
changeInputVal(e) {
const action = changeInputVal(e.target.value);
dispatch(action);
},
add() {
const action = addItem();
dispatch(action);
},
del(index){
const action = delItem(index);
dispatch(action);
}
};
};
//让TodoList与store做连接,如何做连接有个映射关系就是mapStateToProps
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
由于TodoList中没有业务逻辑,只有一个render函数,所以可以把TodoList写成一个ui组件,改造后代码如下
import { connect } from "react-redux";
import { addItem, changeInputVal,delItem } from "./store/actionCreator.js";
const TodoList = (props)=>{
const { changeInputVal, add, list, del,inputVal } = props;
return (
{
list.map((item,index)=>{
return - {del(index)}} key={index}>{item}
})
}
);
}
//中间代码省略...
/**
* 让TodoList与store做连接,如何做连接有个映射关系就是mapStateToProps
* 在一个组件中一般是返回组件,此处返回的是connect函数执行的结果,上面代码改造之后
* TodoList是一个ui组件,connect函数把ui组件和业务逻辑结合,最后得到的是一个容器组件,
* 所以导出的就是一个容器组件
*/
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);