一、相关知识点
1.React是什么?
React 是一个用于构建用户界面的 JAVASCRIPT 库。
2.React 特点
1.声明式设计 −React采用声明范式,可以轻松描述应用。
2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
3.灵活 −React可以与已知的库或框架很好地配合。
4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
3.生命周期
这篇文章的生命周期写的挺好的,感兴趣的小伙伴可以去看一下https://www.jianshu.com/p/514fe21b9914
4.jsx
React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
由于 JSX 就是 JavaScript,一些标识符像 class 和 for 不建议作为 XML 属性名。作为替代,React DOM 使用 className 和 htmlFor 来做对应的属性。
优点:
JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
它是类型安全的,在编译过程中就能发现错误。
使用 JSX 编写模板更加简单快速。
5.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(DoucumentFragment) 和原始的DOM 做比对,找差异
7. 找出input框发生了变化
8. 只用新的DOM中的input元素,替换掉老的DOM中的input元素
缺陷:
性能的提升并不明显
1. state 数据
2. JSX 模版
3. 数据 + 模版 生成虚拟DOM(虚拟DOM就是一个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中的内容
优点:
1. 性能提升了。
2. 它使得跨端应用得以实现。React Native
6.redux 阮一峰老师对redux的使用讲解
这个redux和react交互的过程可以模拟成下面的场景:
一个人(reactComponent)去图书馆借书,需要向图书馆管理员(Store)申请,这个申请的命令(action)传递给了管理员(store),管理员会去他的电脑(reducer)上查看,电脑会接收这个(action以及需要查到这个书的状态previousState),根据借书还是还书的action,然后电脑会将这个书的新的状态(newState),反馈给管理员(store),然后管理员(store)便会将书给这个人(Components),j就是刷新页面,所以说,数据的改变都是通过store来进行改变的,改变的state
二、脚手架工具create-react-app安装
1.使用以下命令进行安装:
npm install -g create-react-app
2.在需要创建项目的位置打开命令行,输入create-react-app + 项目名称的命令
create-react-app todo-list
3.当项目创建完成后,可以进入项目,并启动:
cd todolist
npm start
三、主要目录结构
主要需要关注的就是index.js,index.html,以及App.js(默认生成的事App.js,根据实际情况可以重命名,我这是TodoList.js)
三、具体实现
需求:输入框中输入待办事件,点击提交在下方列表中显示,点击下方列表中的某个待办事项,可以删除
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
这里主要的作用是将TodoList这个组件加载到id是root的标签上
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
主要是存在id="root’的节点
TodoList.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
import store from './store/index';
import { getInputChangeAction, getAddItemAction, getDelItemAction } from './store/actionCreators';
import './style.css';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDel = this.handleItemDel.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button type="primary" onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul> {this.getTodoItem()}</ul>
</div>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<TodoItem
index={index}
content={item}
delItem={this.handleItemDel}
/>
)
})
}
handleStoreChange() {
this.setState(store.getState());
}
handleInputChange(e) {
//参数是对象
// this.setState({
// inputValue:e.target.value
// });
// 参数也可以是箭头函数,异步操作,e获取不到
const value = e.target.value;
// this.setState (() => {
// return {
// inputValue: value
// }
// });
// ES6语法 return 简写
// this.setState (() => ({
// inputValue: value
// }));
const action = getInputChangeAction(value);
store.dispatch(action);
}
handleBtnClick(e) {
// this.setState(() => ({
// list: [...this.state.list,this.state.inputValue],
// inputValue: ''
// }))
//prevState === this.state
// this.setState((prevState) => ({
// list: [...prevState.list,prevState.inputValue],
// inputValue: ''
// }))
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDel(index) {
// const list = [...this.state.list]
// list.splice(index,1)
// this.setState(() => ({
// list
// }));
//此处为了方便将所有操作都放在setState中,这里使用不简写的return返回
// this.setState((prevState) => {
// const list = [...prevState.list]
// list.splice(index,1)
// return { list }
// });
const action = getDelItemAction(index);
store.dispatch(action);
}
}
export default TodoList;
其中jsx的函数中包含一个TodoItem的子组件,所以涉及到了父子传值,以及通过redux传值
TodoItem
import React, { Component } from 'react';
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleDel = this.handleDel.bind(this)
}
render () {
const { content } = this.props;
return (
<div onClick={this.handleDel} >
{content}
</div>
)
}
handleDel () {
const { delItem, index } = this.props;
delItem(index)
}
}
export default TodoItem;
redux相关
store/index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
reducer.js
import { ACTION_INPUT_CHANGE, ACTION_ADD_ITEM, ACTION_DEL_ITEM } from './actionType'
const defaultState = {
inputValue:'',
list:[]
}
// reducer 可以接收state,但是绝不能修改state
export default (state = defaultState, action) => {
const newState = JSON.parse(JSON.stringify(state));
if (action.type === ACTION_INPUT_CHANGE) {
newState.inputValue = action.value;
}
if (action.type === ACTION_ADD_ITEM) {
newState.list.push(newState.inputValue);
newState.inputValue = '';
}
if (action.type === ACTION_DEL_ITEM) {
newState.list.splice(newState.index,1);
}
return newState;
}
actionType.js
const ACTION_INPUT_CHANGE = "action-input-change";
const ACTION_ADD_ITEM = "action-add-item";
const ACTION_DEL_ITEM = "action-del-item";
export {
ACTION_INPUT_CHANGE,
ACTION_ADD_ITEM,
ACTION_DEL_ITEM
}
actionCreators.js
import { ACTION_INPUT_CHANGE, ACTION_ADD_ITEM, ACTION_DEL_ITEM } from './actionType';
const getInputChangeAction = (value) => ({
type: ACTION_INPUT_CHANGE,
value
});
const getAddItemAction = () => ({
type: ACTION_ADD_ITEM
});
const getDelItemAction = (index) => ({
type: ACTION_DEL_ITEM,
index
});
export {
getInputChangeAction,
getAddItemAction,
getDelItemAction
}
补充一:使用react-redux编写todoList
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store';
const App = (
<Provider store={store}>
<TodoList />
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
TodoList.js
import React from 'react';
import { connect } from 'react-redux';
import { inputValueAction, addListItemAction, delListItemAction } from './store/actionCreators';
const TodoList = (props) => {
const { inputValue, changeInputValue, list, handleClickBtn, handleDeleteItem } = props;
return (
<div>
<div>
<input value={inputValue} onChange={changeInputValue} />
<button onClick={handleClickBtn}>提交</button>
</div>
<ul>
{
list.map((item, index) => {
return (
<li
key={index}
index={index}
onClick={handleDeleteItem}
>
{item}
</li>
)
})
}
</ul>
</div>
)
}
// store.state, props
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}
// store.dispatch, props
const mapDispatchToPros = (dispatch) => {
return {
changeInputValue(e) {
const action = inputValueAction(e.target.value)
dispatch(action);
},
handleClickBtn() {
const action = addListItemAction()
dispatch(action)
},
handleDeleteItem(e) {
const action = delListItemAction(e.target.index)
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToPros)(TodoList);
补充二:使用redux-thunk中间件来编写TodoList,异步获取数据(数据利用Charles模拟本地数据)
中间件:https://www.zhihu.com/question/19730582
下图的内容,转自上述链接
redux-thunk中间件是作用于action与store之间的,和react没关系,主要是升级了action,能够使得action不仅可以返回对象,也可以返回函数了,因此主要场景是将复杂的逻辑或者异步操作放在action中操作
store/index.js
import { createStore,applyMiddleware } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const store = createStore (
reducer,
applyMiddleware(thunk)
);
export default store ;
TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
import TodoListUI from './TodoListUI';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this)
store.subscribe(this.handleStoreChange);
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
/>
)
}
componentDidMount() {
const action = getTodoList();
store.dispatch(action);
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState());
}
handleBtnClick() {
const action = getAddItemAction();
store.dispatch(action);
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action);
}
}
export default TodoList;
以上重点就是componentDidMount这个钩子函数,里面的action返回的是一个函数,处理的是一个异步请求,其中可以获取到dispatch
actionCreators.js
import { INIT_LIST_ACTION } from './actionTypes';
import axios from 'axios';
export const initListAction = (data) => ({
type: INIT_LIST_ACTION,
data
});
export const getTodoList = () => {
return (dispatch) => {
axios.get('/list.json').then((res) => {
const data = res.data;
const action = initListAction(data);
dispatch(action);
})
}
}
TodoListUI .js
import React from 'react';
import { Input, Button, List } from 'antd';
const TodoListUI = (props)=> {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input
value={props.inputValue}
placeholder='todo info'
style={{width: '300px', marginRight: '10px'}}
onChange={props.handleInputChange}
/>
<Button type="primary" onClick={props.handleBtnClick}>提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={props.list}
renderItem={(item, index) => (<List.Item onClick={() => {props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
export default TodoListUI;
TodoListUI是一个容器组件,并且是无状态组件
补充三:使用redux-sage中间件来编写TodoList,异步获取数据(数据利用Charles模拟本地数据)
store/index.js
import { createStore,applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import mySaga from './sagas';
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore (
reducer,
applyMiddleware(sagaMiddleware)
);
// then run the saga
sagaMiddleware.run(mySaga);
export default store ;
TodoList.js中的componentDidMount方法
componentDidMount() {
const action = getInitList();
store.dispatch(action);
}
store/sagas.js
import { takeEvery, put } from 'redux-saga/effects';
import { GET_INIT_LIST } from './actionTypes';
import { initListAction } from './actionCreators';
import axios from 'axios';
function* getInitList() {
try {
const res = yield axios.get('/list.json');
const action = initListAction(res.data);
yield put(action);
}catch(e) {
console.log('list.json网络请求失败');
}
}
// generator 函数
function* mySaga() {
yield takeEvery(GET_INIT_LIST, getInitList);
}
export default mySaga;
store.dispatch(action),不仅是reducer能监听到这个action的分发,saga也可以监听到,takeEvery这个方法就是监听的type是GET_INIT_LIST的action,如果监听到后,就会执行getInitList这个方法,这个方法主要就是异步请求数据,请求完了后通过put(action)分发数据
put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect