react 准备工作
-
安装脚手架 npm i create-react-app
-
初始化新建项目 create-react-app 项目名
-
运行 cd 项目名 yarn start
- 项目目录结构
-mode_modules 依赖库
- public :index.html是webpack打包时需要的模板,index.html有一个根节点 此外这里面还可以存放一些公用的js、css文件
- src 代码的源文件 我们写的代码一般都存放在这里
- src/index.js 是程序的主入口 相当于Vue 的 main.js
- index.js 是 webpack打包的起点 index.js负责把 APP.js渲染到页面中的真实DOM中
- src/APP.js相当于App.vue,APP.js 是组后把组件组装到一起的地方
- 项目目录结构
-mode_modules 依赖库
-
组成部分 react.js---核心库 react-dom.js --- 提供操作DOM功能
jsx语法 是 js + html的语法 将组件的HTML结构、数据、甚至样式写在一个 js文件中 最终会被编译成js代码 使用前需引入react 可以写在js文件中 也可以写在jsx文件中
jsx是一种语法糖 目的是为了简化HTML和数据绑定的过程 提高开发的效率
- 注意事项
- 遵循js语法
- 通过ReactDOM.render()渲染到页面中 最外层只有一个根节点
- class ----- className
- for------htmlFor
- style = {}
- 换行用()包起来
- {表达式}
- 并不是一个真正的元素 在渲染页面之前不能当做DOM来操作 jsx经过babel编译会形成普通的js对象 这些对象就是虚拟DOM
- ReactDOM.render(longHTML, document.getElementById('root'), () => {
// 这个回调函数会在 longHTML 渲染到页面之后被调用
// 如果需要操作刚刚渲染到页面中的元素,要在这里面获取和操作它
})
- lonHTML 就是jsx语法经过编译后的虚拟DOM对象
- document.getElementById('root')是用来获取index.html中的根元素
- 后面的回调函数是虚拟DOM挂载到真实DOM上之后执行的回调函数 在这里使用js的DOM操作方法
- ReactDOM.render()的渲染过程
- 执行render时 将虚拟DOM转化成真实的DOM
- 将真实的DOM元素插入到页面中已经存在的DOM元素中
- 调用render的回调函数
列表渲染 根据给定的数据 生成一组相同的元素 在vue中v-for指令做列表渲染 使用map方法
- react 使用map做列表渲染时 在map的回调函数中返回生成多个的元素 列表渲染时需要些key
条件渲染 react条件渲染 需要使用if-else等判断语句 或者使用三元运算符
视图映射
- 数据源有两个
- props 是父组件传过来的 是外部传入的
- state是组件私有的状态 是组件内部的
- 属性props映射视图
- 不管是函数声明还是class类声明的组件 都有props形参 若在标签中使用到了props中的内容(props内容都是从外面传进来的 一般都是动态数据)那么当我们传进来的数据源发生改变的时候 react也会自动更新视图 一般都是父组件通过props向子组件传数据 然后父组件的数据发生变化会导致子组件的视图也会发生变化更新
- 状态(state)映射视图
- 使用state之前 需要在构造函数constructor对象中进行初始化 this.state = {存放需要监听的状态}
- 若想修改state中的属性 需要在class类组件的实例上有一个setState方法 是react提供的修改state的方法 this.setState({})
- 绑定事件的处理 小驼峰命名法 现在class声明的类的组件上写一个方法 方法名(){ //若不做特殊处理 this并不是指当前组件的实例 1. 使用bind预处理函数进行this的改变 2. 使用箭头函数声明函数 }
React.createElement(type,props,...children)
- type 标签类型
- props 行内属性
- children 标签内的子元素
React组件
两种声明方式
- 函数式声明 必须要返回一个jsx元素
function 函数名(props){ return( <div></div> ) } - 类声明 返回一个jsx返回值
class 类名 extends Component{ constructor(props,content){ super() } render(){ return( <div></div> ) } } - 区别
- props 接收的是外部传进来的数据
- state 接收的是组件私有的数据 使用前必须定义
- class定义的组件有this state 生命周期
- function声明的组件 只有props
props校验 需安装第三方类库 prop-type
在类声明propTypes的静态属性
- 值是一个对象 对象的key 就是需要被校验的props
- value 是校验规则 若组件收到的prop不符合规则 会触发控制台警告
- 设置props校验 static propTypes
- 设置props默认值 static defaultProps
class User extends React.Component {
// 设置props的name和age属性必须是字符串和数字类型,isRequired表示必传,boolean类型需要写成bool而不是boolean
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,isRequired
}
// 设置props中的age的默认值为18
static defaultProps = {
age: 18
父子通信 React 是单向数据流 只能从父组件传给子组件 子组件无法传给父组件
父传子
在父组件jsx 中调用子组件的时候 通过
<Son msg = '我是子组件' num = {this.state.num} />
将父组件的状态传递给子组件即可 最后这些写在行内的属性 会被放进子组件的props形参中供其使用
子传父
- 现在父组件中定义一个方法 当子组件需要修改父组件的状态时 将这个方法传递给子组件
<Son msg = '我是子组件' num = {this.state.num} change = {this.changeType}/>
当需要修改父组件的状态时 调用props中的change这个从父组件传过来的方法即可 方法是一个箭头函数 this会到其上级作用域进行查找 然后就会将父组件的状态更改
受控组件 和 非受控组件
受控组件 进行双向数据绑定
-在HTML中 input texteare select这些表单元素 可以根据用户输入变更自身的状态 react中将可变更的状态放到state中 两者结合起来 让react 的state成为单一数据源 这样的组件叫受控组件
- 简单来说 就是 表单的value属性绑定state中的状态 当value的数据发生变化时 更新state中的状态
- 实现 将表单元素的value值绑定state中的状态 然后监听onChange事件
onChange{(e)=>this.setState({num:e.target.value})}
//e 是事件对象 通过事件对象的事件源获取到表单的value 然后进行state的数据更改即可
非受控组件 就是操作DOM
受控组件 表单的数据存储在state上 非受控组件的数据扔人保存在DOM元素上 若想获取元素的value值 通过操作DOM的方式获取
- 在constructor中通过 this.xxx = React.createRef()
- 在当前组件的元素中行内属性使用 ref = {this.xxx}
- 在其他地方获取这个DOM this.xxx.current就能获取到这个对象
- 在ref中直接将元素对象传给组件对行内样式写 ref = {(e)=>{this.xxx = e}}
- 使用时 直接this.xxx就可获取DOM元素
- 在父组件中获取子组件的DOM元素对象
- 用this.xxx = React.createRef() 定义一个ref
- 使用子组件时 通过ref = {this.xxx} 将ref 传递给子组件
- 在子组件上获取我们想要获取的元素 行内属性 ref = 标识名
- 在父组件中通过 this.xxx.current.refs.标识名 即可获取到子组件中绑定ref的元素了
生命周期钩子函数 class声明的组件中有
- constructor
- render
- componentWillMount(){ 组件被挂载之前执行}
- componentDidMount(){组件被挂载之后执行}
- componentDidMount(){组件在销毁之前 在这里解除绑定 消除定时器}
- shouldComponentUpdate(){当数据发生变化时会先执行该函数 返回true 执行后面的钩子 并更新视图 默认返回true}
- componentWillUpdate(){render执行之前执行}
- componentDidUpdate(){视图更新后执行} 子组件的数据更新是在父组件的render之后开始执行
- 初始化:static defaultProps(静态属性方法) =》constructor =》componentWillMount =》render =》componentDidMount
- 数据变化:shouldComponentUpdate =》componentWillUpdate =》render =》子组件数据更新钩子开始顺序执行 =》 componentDidUpdate 子组件的数据更新是在父组件的render之后开始执行
Redux是一种数据管理模式 解决兄弟之间数据不能传递问题 全局托管数据
- 安装
npm i redux --save
store
创建store 需要reducer
- 先导入redux 引入createStore方法
import { createStore } from 'redux'
- 创建store 告诉store托管的数据和修改数据的方法叫 reducer函数
- 里面的数据不能直接修改若需要修改 修改reducer里面的方法
function reducer(state = {num:0},action){
// state 是当前redux托管的数据,state 在 reducer 函数中的默认值就是 state 的初始值
// action 是修改状态具体的动作以及修改状态需要的参数;action 是一个带有 type 属性的对象,而 reducer 就是根据不同的 action.type 返回一个新的状态对象,以此实现修改状态的目的
switch(action.type){
case 'ADD':
return{
num:state.num + action.mount
}
}
//写render函数首先要 return state
return state
}
let store = createStore(reducer)
export default store
原理:上面的reducer函数,reducer函数接受两个参数,第一个是state也就是我们要托管给store的状态,后面的默认值就是状态的初始值,action则是一个对象,store中的状态不允许直接修改,我们就是通过派发action来进行状态修改的,action是一个带有 type属性+其它载荷 的对象,当我们给store派发action 的时候,store就会到这个reducer中来执行,获取我们派发过来的action的type属性到switch中执行对应操作,执行操作后会返回一个对象,这个对象就是要进行store中的状态更换的对象
- 创建store 将createStore 传入 reducer let store = createStore(reducer)
- 使用store
- 导入store import store from '../store'
- 初始化数据 store.getState() 初始化state 在constructor中 初始化数据
- 修改状态 store.dispatch(actionObj)
- store.subscribe(callback) 订阅状态后重新执行的回调函数 以更新视图
reducer合并
reducer是单一状态树 整个应用中所有的数据都保存在store中 所以若有多个状态就会有多个reducer 但是createStore 只能接收一个reducer 因此需要合并
- redux 中的 combinReducers 是用来整合状态的方法 他接收一个对象作为参数 会把多个reducer 合并到一个中
- 整合后会返回一个新的reducer 在创建store时 传入整合后的reducer 状态也会整合
- 使用整合后的store 带有命名空间 在组件中使用时 需要通过对应的命名空间获取状态
动作创建器 actionCreator 写一个函数专门用来生成dispatch需要的action对象 这个函数称为action-creator 这样在dispatch时 只需要调用action-creator 并且传入payload即可
redux 模块拆分
React-redux的定义和使用 为了代码简洁
- 安装
yarn add react-redux --save
- react-redux的Provider组件 Provider组件将store引入到组件中 可以简化在组件里初始化状态 修改组件 需要派发事件 然后还需要组件订阅更新
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
使用 在Provider在主入口 引入store 通过props 把store作为属性 传给 Provider
- react-redux 的connect方法 使组件中的数据以及派发数据的方法全部由props来实现
import { connect } from 'react-redux'
- 使用连接后的组件的话 就不能跟以前那样导入组件对象了 应该导出连接后的组件方法执行后的返回对象 不能写export default class Counter extends Component{...} 要写成export default connect(state => ({...state.counter}), actions)(Counter) 我们使用store进行数据托管 react-redux 的Provider组件树 和 连接组件的connect的话 若不需要自己的state就可以不写constructor 不影响 this.props进行取值
使用react-redux来进行store的托管 所以这里不能直接导出组件类了 需要导出connect链接组件方法返回 connect连接组件方法是一个高阶组件 connect方法 可以执行两次 第一次执行有两个函数参数
- 第一个函数参数是mapStateToProps(state):在主文件index.js中使用了react-redux组件树Provider并为其传入我们的store对象作为props 这里的state参数 就是Provider组件树中传入的store对象的state属性 也就是store.getState()的返回结果 而这个函数中可以返回一个对象 这个对象的属性会被当做props传给该组件 state也是一个对象 其中存放了我们用的属性 可以直接写成箭头函数 state=>({...state})
- 第二个函数参数是mapDispatchToProps(dispatch) 参数是我们组件树中传入store的store.dispatch方法 我们可以在这个函数中返回对象 对象中存放我们更改store数据的方法(dispatch({...}))这样这个对象中的属性会被当做props穿件组件中去 可以简写成actionCreator对象(action创建器) 第二次执行需要我们传入组件对象 这样会把第一个函数执行的返回的两个对象的属性当做props传到这个组件中 在这个组件中就可以通过this.props.xxx来进行store中数据的取值 或者action执行进行改值了
redux中间件 使用之前必须安装 shiyong中间件 需要将redux中的applyMiddleware导出 同时导入中间件 在调用createStore时传入第二个参数applyMiddleware(中间件名字)
- redux-logger 使用该组件时 store中的状态发生改变时 在控制台将改变前改变后的状态打印出来
- redux-thunk 需要异步操作时 无法直接在actionCreator中进行 使用该中间件 可以让actionCreator返回一个function函数 这个函数的参数是dispatch 和getState 就是store.dispatch 和 store.getState() 它将dispatch的控制权交给actionCreator返回的函数这个函数 中 可以任意位置dispatch
- redux-promise 处理异步数据 常用Promise 该中间件 是用来处理redux中的promise
在actionCreator中有两种方法
- 直接在某个actionCteator中返回一个Promise实例 只能处理resolve 并在resolve时传入一个action对象 若reject 直接抛出错误
import * as Types from '../action-types'
export default { add (amount) { return (dispatch, getState) => { setTimeout(() => { dispatch({ type: Types.ADD, amount }) }, 2000) } }, minus (amount) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ type: Types.MINUS, amount }) }, 2000) }) } }
```
- 若成功和失败都要处理 不能直接在actionCreator函数中返回Promise实例 而是在返回action对象中增加 payload属性 属性值是一个Promise 若Promise resolve那么payload代表的就是resolve时传入的值 若reject那么payload代表的就是resolve时传入的值 同时reducer函数也需要修改
action/index.js
import * as Types from '../action-types'
export default {
add (amount) {
return (dispatch, getState) => {
setTimeout(() => {
dispatch({
type: Types.ADD,
amount
})
}, 2000)
}
},
minus (amount) {
return {
type: Types.MINUS,
payload: new Promise((resolve, reject) => {
// resolve(1)
reject(10)
})
}
}
}
reducer/index.js
import * as Types from '../action-types'
export default function (state = {num: 0}, action) {
switch (action.type) {
case Types.ADD:
return {
num: state.num + action.amount
}
case Types.MINUS:
return {
num: state.num - action.payload // actionCreator 函数使用 Promise 对应修改为减去 action.payload
}
}
return state
}
组合
react 中也有类似与slot的机制 既可以获取标签中包裹的内容 这种机制称为组合 获取组件标签包裹内容通过props.children
- 通过this.props.children获取包裹内容(插入全部内容到指定位置) 在子组件中需要使用父组件中使用子组件包裹的内容 使用{this.props.children}放置在我们需要放置父组件使用子组件包裹的内容的位置上 最后这个this.props.chilidren会被替换成父组件使用子组件时在子组件标签中包裹的内容
- 使用props来插入(插入指定内容到全部位置) 在子组件中使用{this.props.属性名}来放置,然后在父组件中使用子组件的时候通过props将jsx元素传过来 ##Context 提供一种在组件之间共享此类值的方法 不必显示地 通过组件树逐层传递props
- 创建一个Context React.createContext(defaultValue) defaultValue是一个默认值
- 通过Context.Provider 项组件树中引入要共享的值 值通过value属性指定
- App组件需要和ThemeButton共享颜色 且App为上层组件 所以它需要提供这个主题色
- Context.contextType
- 若是class声明的组件 底层组件使用上面的Provider提供的值 需要在组件中 声明contextType静态属性 值是要获取得值的Context
- 然后在组件中就可以通过this.context获取Provider的value属性在指定的共享的值
- 若在constructor中使用context,constructor的第二个参数就是context的值 react会向上层寻找 找到最近的ThemeContext的Provider的value - - - 这个value会传给constructor的第二个参数
- 在constructor以外其他函数中可以通过this.context刚问该值 render函数中也可以
import React,{Component} from 'react'
//创建一个context
const ThemeContext = React.createContext('light')
class ThemeButton extends Component{
static contextType = ThemeContext
constructor(props,context){
super()
console.log(context)
}
render(){
return(
<div>
<button className = {`btn btn-${this.context.theme}`}>{this.context.theme}</button>
</div>
)
}
}
class Toolbar extends Component {
render () {
return <div>
<ThemeButton />
</div>
}
}
export default class App extends Component {
render () {
return (<div>
<ThemeContext.Provider value={{theme: 'success'}}>
<Toolbar />
</ThemeContext.Provider>
</div>)
}
}
- Context.Consumer 在函数中使用context 在函数式的组件中使用Context共享数据的值 需使用Consumer组件
高阶组件 connect 将很多代码功能相同的部分抽离成一个高阶组件 把不同的部分当成参数 可以被连续执行两次及以上的函数(通过必报保存第一次数据然后返回一个函数工第二次执行 与Function.bind类似)
react 的高阶组件实现 指定 connect两次后执行会返回一个组件 第一次执行传入mapStateToProps 和 mapDispatchToProps两个参数 第二次执行传入组件 然后返回一个组件