工程初始化
create-react-app todolist
cd todolist
yarn start
工程目录文件简介
src/index.js 入口文件
src/app.test.js 自动化测试
registerServiceWorker
PWA progressive web application
部署在https协议的服务器上
可实现客户端离线加载
public/manifest.json 可将应用存在桌面
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [ // 快捷方式图标
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".", // 打开的地址
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
react中的组件
自定义组件大写开头
src/index.js
import React from 'react'; // 需要引入
import ReactDOM from 'react-dom';
import App from './App';
// JSX <App />
ReactDOM.render(<App />, document.getElementById('root'));
reactDOM 将组件挂载在dom节点下
src/app.js
import React, { Component } from 'react'; // React需要引入
class App extends Component {
render() {
// JSX
return (
<div>
hello
</div>
);
}
}
export default App;
React需要引入,因为使用了JSX语法
render函数必须返回根节点
react 16开始, render支持返回数组
import React from 'react';
export default function () {
return [
<div>一步 01</div>,
<div>一步 02</div>,
<div>一步 03</div>,
<div>一步 04</div>
];
}
React 16为我们提供了Fragments。
Fragments与Vue.js的<template>功能类似,可做不可见的包裹元素。
import React, { Fragment } from 'react'
export default function () {
return (
<Fragment>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
<div>一步 04</div>
</Fragment>
);
}
实现todoLIst的增加删除功能
handleBtnClick() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})
}
handleItemDelete(index) {
// immutable
// state 不允许我们做任何改变
const list = [...this.state.list]; // 拷贝list
list.splice(index, 1);
this.setState({
list: list
})
}
JSX语法细节补充
JSX写注释
花括号里面是js代码
<div>
{/* 多行注释 */}
{
// 单行注释,必须换行
}
<input
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
用className替代class,避免与ES6 class关键字冲突
import './style.css'
<input
className='input'
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
dangerouslySetInnerHTML的使用
为了防止XSS攻击,在react中,通过富文本编辑器进行操作后的内容,会保留原有的标签样式,并不能正确展示。
在显示时,将内容写入__html对象中即可。具体如下:
<div dangerouslySetInnerHTML = {{ __html: checkMessages.details }} />
如果是直接调用接口中的值,则是以上的写法,如果是单纯的显示固定的内容,用如下的写法:
<div dangerouslySetInnerHTML={{ __html: '<div>123</div>' }} />
用htmlFor代替label标签的for
<div>
<label htmlFor="inserArea">输入内容</label>
<input
id='inserArea'
/>
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
组件间通过props传值
// Todolist.js
return (
<div key={index}>
<ToDoItem
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
</div>
)
// TodoItem.js
render() {
const { content } = this.props;
return (
<div onClick={this.handleClick}>
{content}
</div>
)
}
handleClick() {
const { deleteItem, index } = this.props;
deleteItem(index);
}
TodoList代码优化
在constructor中绑定this,提高性能
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this)
}
代码拆分
// 使JSX更简洁
<ul>
{this.getTodoItem()}
</ul>
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<ToDoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
异步更新状态
handleInputChange(e) {
const value = e.target.value;
// 用函数代替对象
this.setState(() => ({
inputValue: value
}))
}
该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数
handleBtnClick() {
this.setState((prevState, props) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}))
}
围绕react衍生出的思考
1.声明式开发
节约大量DOM操作代码
相对于命令式开发 jQuery操作DOM
2.可以与其它框架并存
react只管理一个DOM
3.组件化
4.单向数据流
子组件不能修改父组件传过来的值
只能调用父组件的方法修改数据
5.视图层框架
6.函数式编程
为自动化测试提供了方便
PropTypes与DefaultProps的应用
首先,引入prop-types包(脚手架自带)
import ProTypes from 'prop-types'
ToDoItem.propTypes = {
test: PropTypes.string.isRequired,
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number
}
ToDoItem.defaultProps = {
test: 'todo'
}
props,state与render函数的关系
当组件的state或者props发生改变的时候,render函数就会重新执行
React中的虚拟DOM
- state 数据
- JSX 模板
- 数据 + 模板 结合,生成真实的DOM,来显示
- state 发生改变
- 数据 + 模板 结合,生成真实的DOM,替换原始的DOM
缺陷:
第一次生成了一个完整的DOM片段
第二次生成了一个完整的DOM片段
第二次的DOM替换第一次的DOM,非常耗性能\
- state 数据
- JSX 模板
- 数据 + 模板 结合,生成真实的DOM,来显示
- state 发生改变
- 数据 + 模板 结合,生成真实的DOM,并不直接替换替换原始的DOM
- 新的DOM和原始的DOM作比对,找差异
- 找出input框发生了变化
- 只用新的DOM中的input元素,替换掉老的DOM中的input元素
缺陷:
性能提升不明显
-
state 数据
-
JSX 模板
-
生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实的DOM)(损耗了性能)
['div', {id: 'abc'}, ['span', {}, 'hello world']] -
数据 + 模板 结合,生成真实的DOM,来显示
<div id="abc"><span>hello world</span></div>
-
state 发生改变
-
数据 + 模板 生成新的虚拟DOM (极大的提升了性能)
['div', {id: 'abc'}, ['span', {}, 'btyh']] -
对比原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容
-
直接操作DOM,改变span中的内容
优点:
- 性能提升了
- 它使得跨端应用得以实现。 React Native
深入了解虚拟DOM
// JSX -> createElement -> JS对象 -> 真实的DOM
return <div>item</div>
// 相当于
return React.createElement('div', {}, 'item')
虚拟DOM中的Diff算法
Diff difference
把多次setState结合成一次setState,减少虚拟DOM比对的次数
Diff算法会逐层比对,如果一层不满足匹配就不会继续往下比对,用新的替换老的\
React中ref的使用
采用回调 Refs的方式
<input
id='inserArea'
className='input'
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => {this.input = input}}
/>
可以直接访问DOM元素
handleInputChange(e) {
// const value = e.target.value;
const value = this.input.value;
this.setState(() => ({
inputValue: value
}))
}
尽量避免使用ref
React的生命周期函数
生命周期函数是指在某一个时刻组件会自动调用执行的函数
1.Initialization 组件初始化阶段
setup props and state 在constructor中执行
2.Mounting 组件第一次挂载阶段
componentWillMount -> render -> componentDidMount
3.Updation 组件更新
props改变 -> componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
state改变 -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
shouldComponentUpdate如返回false后面不再执行
componentWillReceiveProps执行条件:1.一个组件要从父组件接收参数 2.父组件的render函数被重新执行
4.Unmounting 组件卸载
componentWillUnmount
生命周期函数的使用场景
-
父组件render函数重新执行,子组件render函数也会重新执行,这样会造成性能浪费
解决方法:
// 在子组件TodoItem中 shouldComponentUpdate(nextProps, nextState) { if (nextProps.content !== this.props.content) { return true; } else { return false; } }
-
在componentDidMount执行Ajax数据请求,因为componentDidMount只执行一次
使用react-transition-group实现动画
Redux
Redux = Reducer + Flux
工作流程
![](https://img.haomeiwen.com/i11156200/c65733961fcc46af.jpg)
Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
Redux 提供createStore这个函数,用来生成 Store。
import { createStore } from 'redux';
const store = createStore(reducer);
State
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。
import { createStore } from 'redux';
const store = createStore(reducer);
const state = store.getState();
Action
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置。
{type: ”ADD”, key1: ”“,key2: ”“}
Action Creator
View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) {
// ...
return new_state;
};
每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。
const actions = [
{ type: 'ADD', payload: 0 },
{ type: 'ADD', payload: 1 },
{ type: 'ADD', payload: 2 }
];
const total = actions.reduce(reducer, 0); // 3
三个基本原则
- store必须是唯一的\
- 只有store能够改变自己的内容\
- Reducer必须是纯函数
纯函数指的是,给固定的输入,就一定会有固定的输出。而且不会有任何副作用
数据流
1.调用 store.dispatch(action)
2.Redux store 调用传入的 reducer 函数。
3.根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
4.Redux store 保存了根 reducer 返回的完整 state 树。
Redux核心API
createStore
store.dispatch
store.getState
store.subscribe
redux-thunk中间件
让store.dispatch方法可接收函数
步骤:
1.yarn add redux-thunk 安装
2.与REDUX_DEVTOOLS一起使用
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
3.把异步操作移到actionCreator
export const getTodoList = () => {
return (dispatch) => {
axios.get('/list.json').then((res) => {
const data = res.data;
const action = initListAction(data);
dispatch(action);
})
}
}
4.在组件中提交action
componentDidMount() {
const action = getTodoList();
store.dispatch(action);
}
中间件
action 跟reducer的中间,对dispatch方法的包装升级
applyMiddleware源码
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer);
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {...store, dispatch}
}
}
React-redux的使用
Provider组件
connect 连接组件和store
mapStateToProps 组件和store的映射
mapDispatchToProps store.dispatch挂载到props
网友评论