1.react中几个核心概念
提示:react中的一个重要理念是 all in js
react关注的是数据的操作,是一个数据驱动的框架,state
或 props
中的数据发生变化的时候,render
函数就会被重新执行;同样,父组件传递的数据发生变化时,子组件也将重新执行 render
函数。而不是DOM操作,数据发生变化,对应的DOM就会改变
虚拟DOM
- DOM的本质是: 浏览器中的概念,用js对象表示页面上的元素,并提供了操作DOM元素的API。
- React中的虚拟DOM是: 是框架中的概念,是程序员用js对象来模拟页面上的DOM和DOM嵌套。
- 使用虚拟DOM的目的: 为了实现页面中的DOM元素的高效更新。
- 虚拟DOM和ODM二者间的区别:
- 浏览器提供的概念,用js对象来表示页面中的元素,并未元素提供了相应的API。
- 虚拟DOM:框架中的概念,是开发框架的程序员,手动用js对象来模拟DOM元素及其嵌套关系的。
- 本质:用js对象来模拟虚拟DOM元素和嵌套关系
- 目的:为了实现元素的高效更新
//虚拟DOM底层原理的实现
1.state数据
2.JSX模板
3.数据+模板 生成虚拟DOM(虚拟DOM就是一个js对象,用它来描述真实的DOM) ==>(损耗了性能)
['div',{节点属性},[子节点]]
['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',{},'hello you']]
7.新旧虚拟DOM作对比,找到不同就更新 ==>(极大提升了性能)
8.直接操作DOM,改变span中的内容
优点:性能提升了
使得跨端应用得以实现。React Native
温馨提示
React中生成DOM或者 对比DOM是非常耗性能的。
而通过JSX生成的js对象并去对比js对象只会消耗极小的性能,这样整体下来提升了性能。
2.Diff算法(different)
-
tree diff: 新旧两颗DOM数,逐层对比的过程,就是Tree Diff;当整个DOM逐层对比完成之后,则需要按需更新的元素必然能够找到。
-
component diff: 在进行 tree diff 的时候,每一层,组件级别的对比,就是 组件树。
-
注意:
- 如果对比前后,组件类型相同,则暂时认为组件不需要被更新。
- 如果对比前后,组件类型不同,则需要移除就组件,创建新组件,并追加到页面中去。
-
element diff: 在对比组件的时候,如果两个组件类型不同,则需要进行元素级别的对比,这叫做 element diff.
3.registerServiceWorker
我们使用create-react-app脚手架时,会发现入口文件index.js中有这样一个引入使用。
Http 协议上的服务,PWA:progressive web application
这个文件可以视情况用或者不用,它是用来做离线缓存等任务的,实际上就是为react项目注册了一个service worker。这样的话,如果在线上,只要访问过一次该网站,以后即使没有网络也可以访问(此时使用的是之前缓存的资源)。只在生产环境中有效(process.env.NODE_ENV === ‘production’)
在项目的public目录下,会有一个manifest.json文件,可以在这里做一些配置(图标、名字等等)。当用户把网页生成一个快捷方式时。会感觉用起来像原生APP一样。
4.JSX语法
- 注意:react项目中使用到
jsx
语法时,必须引入react
包
import React,{Component,Fragment} from 'react';
//JSX -> React.createElement() -> 虚拟DOM(JS对象) -> 真实的DOM
class App extents Component{
constructor(){ //最优先执行,构造方法
super(props);
//数据定义在状态中state中
this.state = {
test:'test',
}
/*
使用 jsx 语法时必须导入react包,并且使用js代码时,需要放在 {} 中
改变state数据时,需要使用 this.setState({}) 不能直接改变state下的值
组件上绑定方法的时候,注意 通过 .bind(this)
事件不同原生的事件 应该是 onClick
或者在此处声明: this.function = this.function.bind(this);
*/
}
render(){
return (
//可以用Fragment占位符来处理根标签
<Fragment>
<div>Test</div>
<ul>
<li>react</li>
<li>redux</li>
</ul>
</Fragment>
)
}
}
export default App;
<!--传统方式-->
<script type="text/babel">
//1.创建变量
let myClass = "span";
let myContent = "你好,世界!";
//2.创建虚拟DOM
const vDom = React.createElement('span',{className:myClass},{myContent});
//3.将虚拟DOM渲染到页面上去
ReactDOM.render(vDom,document.getElementById("app1"));
</script>
5.子父组件间的传值
- 父组件向子组件
父组件通过属性来传值,子组件通过this.props.属性值
来获取数据
在子组件中一般会通过变量来声明父组件传递过来的属性名称,并指定类型。
type Props = {
name: String,
func: Function,
}
class App extends Component<Props> {
render() {
//使用 this.props.name 来获取值属性值
}
}
- 子组件向父组件
父组件通过属性的传递方式将方法传递给子组件,子组件通过this.props.方法名
来调用方法,从而实现将数据传递给父组件。
注意:父组件向子组件传递方法,并且子组件需要执行该方法的一定时候,一定要留意this
的指向问题。
6.ES6语法(可以用来优化代码)
-
可以通过解构赋值的方式来获取
state
下的值
const { test } = { this.state }
-
可以将
this.setState({})
写成es6高级语法this.setState({},callback) 属于异步函数,可以通过回调函数来处理响应的操作。
this.setState(() => {
return {***};
});
this.setState(() => ({***}));
this.setState((prevState) => ({***})); //prevState 相当于是 this.state
7.React生命周期函数
https://blog.csdn.net/zlq_CSDN/article/details/94971594
8.React中组件概念
- UI组件: 负责的是页面的渲染问题,也称为“傻瓜组件”;
// UI组件
class TodoListUI extends Component{
render() {
return (
<div style={{marginTop:20,marginLeft:20}}>
<Input
style={{width:300}}
value={this.props.inputValue}
onChange={this.props.handleInputChange}
/>
<Button onClick={this.props.handleClick} type="primary">提交</Button>
<List
style={{width:300,marginTop:10}}
bordered
dataSource={this.props.list}
renderItem={(item,index) => (
<List.Item onClick={(index) => {this.props.handleClickDelete(index)}}>
{item}
</List.Item>
)}
/>
</div>
)
}
}
- 容器组件: 来使用UI组件,并且负责组件中的逻辑问题,也称为“聪明组件”;
- 无状态组件: 实际上是一个函数,一个组件中,只有 render 时,就可以改变为一个无状态组件,性能比UI组件高。
// 无状态组件(作为一个函数,将模板渲染出去,参数props来接收父组件传递的值)
const TodoListUI = (props) =>{
return (
<div style={{marginTop:20,marginLeft:20}}>
<Input
style={{width:300}}
value={props.inputValue}
onChange={props.handleInputChange}
/>
<Button onClick={props.handleClick} type="primary">提交</Button>
<List
style={{width:300,marginTop:10}}
bordered
dataSource={props.list}
renderItem={(item,index) => (
<List.Item onClick={(index) => {props.handleClickDelete(index)}}>
{item}
</List.Item>
)}
/>
</div>
)
}
9.redux-thunk中间件进行ajax请求发送
注意:中间件是属于 redux
的,而不是react 。中间可以理解为是在某两个部分之间建立的连接(redux-thunk中间件在action
与store
之间建立了连接)。使用了 redux-thunk
中间件后,在action中使用时,可以返回一个函数。
实质上是对 dispatch
方法的一个升级。具体体现:
- 当action中返回一个对象的时候,会直接传递给store。
- 当action返回一个函数的时候,会先执行函数。
- 由于中间件会跟 redux-devtools 一起使用,所以需要事先在
store
下的index.js
中进行配置。
具体配置方法参考:https://github.com/reduxjs/redux-thunk
https://github.com/zalmoxisus/redux-devtools-extension#usage
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
//获取到数据
const store = createStore(reducer, enhancer );
export default store;
- 在
actionCreatoer.js
中编写方法
// 使用 redux-thunk 中间件来进行ajax请求,可以返回一个函数
export const getTodoList = () => {
return (dispatch) => { //返回的这个函数会自动的接收dispatch方法
axios.get('/list.json').then((res) => {
const data = res.data;
const action = getInitItemAction(data);
console.log(action);
dispatch(action); //真正的将action提交到 index.js 中
});
}
}
- 在生命周期函数中使用
// 生命周期函数来获取
componentDidMount(){
//通过store.getState() 来获取store中的数据
console.log(store.getState());
const action = getTodoList(); //此处的action是个方法
store.dispatch(action)
}
10.redux-saga 中间件来发送ajax请求
配置了该中间件后,当通过 store.diapatch()
提交 action
后,就可在 sagas.js
文件中监听到 action。
- 在
store
下的index.js
中进行配置
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import todoListSage from './sagas'; //需要在store目录下创建文件 sagas.js
const sagaMiddleware = createSagaMiddleware(); //创建中间件
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
//获取到数据
const store = createStore(reducer, enhancer );
sagaMiddleware.run(todoListSage); //运行文件
export default store;
- 创建文件
sagas.js
// 通过 takeEvery 来捕获 dispatch 方法 put 来提交
import {takeEvery, put} from 'redux-saga/effects';
import {GET_INIT_TODO} from './actionTypes'; //规范action名称类型
import {getInitItemAction} from './actionCreator';
import axios from 'axios';
// 在该方法中进行 ajax 数据请求
function* getInitList(){
try{
const res = yield axios.get('./list.json'); //等待ajax数据请求完毕返回给res
const action = getInitItemAction(res.data);
yield put(action);
}catch(e){
console.log('list.json数据请求失败');
}
}
// 当配置了 redux-saga 后,在 sagas.js 中也能够监听到 dispatch 方法
// generator函数(es6 内容)
function* mySaga() {
yield takeEvery(GET_INIT_TODO, getInitList); //当监听到dispatch方法后,就会执行 getInitList 方法
}
export default mySaga;
详情请参考:https://github.com/redux-saga/redux-saga
性能优化
- 将方法作用域绑定在
constructor
中,而不在组件中绑定方法的时候来.bind(this)
来绑定
constructor(props){
this.handleclick = this.handleclick.bind(this);
}
this.setState({})
中有内置的性能提升机制,属于异步函数(可以回调处理),将多次数据的改变集成到一次改变中,从而降低了虚拟DOM的匹配频率。- 使用生命周期函数
shouldComponentUpdate()
shouldComponentUpdate(nextProps,nextState){
if(***){ //条件表示当数据发生变化的时候 比如:if(nextProps.content !== this.props.content) 前后两次的属性内容发生了变化
return true;
}else{
return false;
}
}
细节总结
- 使用 className,而不是class
- 使用 htmlFor,而不是for,jsx中默认class、for是保留字。
<label htmlFor=""></label>
- dangerouslySetInnerHTML={{ __html=item }} //item表示渲染的数据,整体上表示解析html代码
- propTypes、defaultTypes 属性校验 https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#___gatsby
- ref 来获取DOM 元素
//ref属性支持回调
<button ref={(btn) => {this.ul = ul}}></button> //this.ul 指向的就是 当前DOM对象 可以在js中直接通过this.ul来访问对象
- map遍历
在使用map遍历的时候,需要添加key
属性,否则会有警告。
key需要添加在遍历渲染元素的最外层。
思考,react特点
- 声明式的开发
- 可以与其他框架并存
- 组件化 (与标签的区别,组件首字母大写)
- 单项数据流(子组件只能使用父组件的数据,而不能直接来修改数据,只能通过传递参数的形式来改变数据)
- 视图层(当组件层级过多的时候,单靠react是不能够来实现项目的开发,需要借助类似于
redux
来管理) - 函数式的编程 (都是通过函数来进行对数据的处理,从而来更新DOM)
- 版本上线,同Vue项目
npm run build