基础
依赖
- react.js
- react-dom.js
- babel.js
创建虚拟DOM
const VDOM = <h1>hello</h1> //DOM是一个object对象
渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('root'))
jsx
- 更加简单的创建虚拟DOM
- 样式名: className
- 内联样式:style={{color: 'red'}}
- 虚拟DOM只有一个根标签
- 标签必须闭合
函数式组件
- 组件名必须首字母大写
- 组件内必须return
- 组件内的this为undefined,因为babel编译时开启了严格模式
function Demo(){
return <h1>demo</h1>
}
// 渲染时Demo标签必须闭合
ReactDOM.render(<Demo />, document.getElementById('root'))
类式组件
- 必须继承React.Component
- 必须有render
- 必须return
- 类中可以直接写赋值语句:a=1,给所有类的实例对象添加一个属性a,值为1
执行类式组件
- 找到Demo组件
- 发现是类定义的组件,new出来该类的实例,并通过该实例调用原型上的render方法
- 将render返回的虚拟DOM转为真实DOM,渲染到页面上
class Demo extends React.Component{
render(){
return {
<h1>demo</h1>
}
}
}
// 渲染时Demo标签必须闭合
ReactDOM.render(<Demo />, document.getElementById('root'))
组件实例的三大核心属性
state
- 不可直接更改,必须通过内置API
setState进行更改 - setState是对state进行合并,而不是替换
props
<Person name="tom" age="18"/>
- props是只读的
- 限制props: PropTypes 作用:验证父组件通过属性传递过来的数据的数据类型
- 导入 prop-types
import PropTypes from 'prop-types' - 如何验证:要在组件的 props 上进行类型检查,你只需配置特定的
propTypes属性:// 子组件 class One extends Component { // 属性验证写法二: static propTypes = { num:PropTypes.number } } // 属性验证写法一: // One.propTypes = { // num:PropTypes.number // } // 父组件 export default class Two extends Component { render(){ return <One num={1}></One> } }-
单个条件
static propTypes = { num:PropTypes.number } -
多个条件
static propTypes = { num:PropTypes.oneOfType([ PropTypes.number, PropTypes.string, PropTypes.bool, PropTypes.array, PropTypes.object, PropTypes.func ]) } -
限制数组里面的元素由某一种元素组成
static propTypes = { num:PropTypes.arrayOf(PropTypes.number) } -
限制数组里面的元素由某几种类型的元素组成
static propTypes = { num:PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])) } -
指定一个对象由某一类型的值组成
static propTypes = { num:PropTypes.objectOf(PropTypes.number) } -
指定对象中的某属性的值由特定的类型组成
static propTypes = { num:PropTypes.shape({ a:PropTypes.number, b:PropTypes.string }) } -
指定对象中某属性的值由某类型组成
static propTypes = { num:PropTypes.shape({ a:PropTypes.oneOfType([ PropTypes.number,PropTypes.string, PropTypes.func]), b:PropTypes.string }) } -
对象中某属性值不能为空
static propTypes = { num:PropTypes.shape({ id:PropTypes.number.isRequired }) } -
枚举,指定是某个值
static propTypes = { num:PropTypes.oneOf([1, 2]).isRequired }
-
refs
字符串形式的ref--不推荐使用了
<input ref="input1">- 使用
- {input1} = this.refs 得到的是真实DOM
- input1.value 得到的是输入框的值
回调refs
<input ref = {c=> this.input1 = c}- 其中c为当前节点(currentNode)
- 给当前实例(this)新增一个属性inpu1,值为c
- 此时input上的
ref函数是一个回调函数,不需要我们自己调用,react会自动调用 - 回调refs已内联函数定义,更新过程中,会被执行两侧,第一次传参为null,第二次才为节点
- 使用
- {value} = this.input1
- 解决调用两次的问题
<input ref = {this.saveInput} />,saveInput是class的绑定函数- class里
saveInput = c => { this.input1 = c }
creatRef 专人专用,只能存一个
- class里
- myRef = React.creatRef()
- 返回容器(一个可以存储被ref所标识的节点)
<input ref={this.myRef} />
注:也可以通过event.target.value得到输入框的值
<input onBlur={this.show} />
show = event => {
console.log(event.target.value)
}
受控组件与非受控组件
非受控组件
- 现用现取
<input ref={c => this.username = c} />- 用时:this.username.value
受控组件
- 存在
state中 <input onChange={this.saveUsername}/>
saveUsername = e => {
this.setState({username: e.target.value})
}
- 使用: const username = {this.state}
- 受控组件不用使用ref
函数的柯里化(这里是为了参数)
- 通过函数调用继续返回函数
<input onChange={this.saveFormData('username')}/>- saveFormData后面跟了小括号和参数,所以这个函数一开始就会调用
saveFormData = dataType => {
return (event) => {
//这个返回的箭头函数才是onChange时调用的函数
this.setState = [dataType]: event.target.value
}
}
- 不用柯里化
<input onChange={event => this.saveFormData('username', event)}/>
saveFormData = (dataType, event) => { this.setState = [dataType]: event.target.value }
生命周期 V16.8
- componentWillMount(): 组件将要挂载
- componentDidMount(): 组件挂载完毕
- 初始化:开启定时器、发送网络请求、订阅消息
- shouldComponentUpdate(): 控制组件更新的阀门,默认为true
- componentWillUpdate(): 组件将要更新
- componentDidUpdate(): 组件更新完毕
- componentWillReceiveProps(): 组件将要接收props(第一次不算)
- componentWillUnmount(): 组件将要卸载
- 收尾:关闭定时器、取消订阅消息
渲染
- 初始化阶段:由ReactDOM.render()触发
- constructor()
- componentWillMount()
- render()
- componentDidMount()
- 更新阶段:由组件内的
this.setState()或父组件的render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
- 卸载阶段:由ReactDOM.unmountComponentAtNode(document.getElementById('test'))触发
- componentWillUnmount()
强制更新 forceUpdate
- 不更改任何状态的数据,也可以更新页面
- 阀门关了也能更新
生命周期 V17.0
- 废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate
- 新增
- static getDerivedStateFromProps:会在初始化和update时触发,用于替换componentWillReceiveProps,可以用来控制 props 更新 state 的过程;它返回一个对象表示新的 state;如果不需要更新,返回 null 即可
- getSnapshotBeforeUpdate: 用于替换 componentWillUpdate,该函数会在update后 DOM 更新前被调用,用于读取最新的 DOM 数据,返回值将作为 componentDidUpdate 的第三个参数
class A extends React.Component {
// 用于初始化 state
constructor() {}
// 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
// 因为该函数是静态函数,所以取不到 `this`
// 如果需要对比 `prevProps` 需要单独在 `state` 中维护
static getDerivedStateFromProps(nextProps, prevState) {}
// 判断是否需要更新组件,多用于组件性能优化
shouldComponentUpdate(nextProps, nextState) {}
// 组件挂载后调用
// 可以在该函数中进行请求或者订阅
componentDidMount() {}
// 用于获得最新的 DOM 数据
getSnapshotBeforeUpdate() {}
// 组件即将销毁
// 可以在此处移除订阅,定时器等等
componentWillUnmount() {}
// 组件销毁后调用
componentDidUnMount() {}
// 组件更新后调用
componentDidUpdate() {}
// 渲染组件函数
render() {}
// 以下函数不建议使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
样式模块化
import hello from './indedx.moduke.css'<h2 className={hello.title}></h2>
子组件向父组件传递数据
- 父组件给子组件传递一个函数,子组件调用
- 父组件:
<Header addTodo={this.addTodo} />- 父组件向子组件批量传递数据:
<Item {...todo}/>: 展开todo,批量传递
- 父组件向子组件批量传递数据:
addTodo = todoObj => {
console.log(todoObj)
}
- 子组件: this.props.addTodo({id: 1, title: 'xxx'})
- nanoid插件: 获取唯一的id
- npm i nanoid
- import {nanoid} from 'nanoid'
- nanoid()
消息订阅发布 PubSubJs
- npm i pubsub-js
- import PubSub from 'pubsub-js'
- PubSub.publish('test', {name: 'zs', age: 18})
- PubSub.subscribe('test', (_, data) => {...})
fetch
- 浏览器内置
- promise风格
const url = 'xxxx'
try{
const res = await fetch(url)
const data = res.json()
console.log(data)
}catch(err){
console.log(err)
}
React-Router
- 使用的插件是
react-router-dom import { Link, Route, BrowserRouter, NavLink, Switch, Redirect } from 'react-router-dom'- 导航区:
<Link className="" to="/about">About</Link> - 导航区:
<NavLink activeClassName="active" to="/about">About</NavLink>: 可以自定义选择的样式,默认为active - 展示区:
<Route path="/about" component={About}/>- About 是路由组件,一般放在pages文件夹下
- 一般组件放在component文件夹下
- Link/NavLink: 路由连接,引起路由变化
- replace属性:使用该属性,跳转路由将不会在history中push一个浏览记录,而是取代上一个浏览记录。replace={true}, 或直接写replace
- Route:注册路由
- Redirect: 重定向,一般作为默认路由,放在Route的最下方
- Switch: 用Switch组件把Route组件包裹,不会再继续往下匹配,可提高效率(一个以上路由)
- exact: Route的属性,精准匹配(需要才开,不然会影响二级路由的匹配)
- BrowserRouter/HashRouter: 路由器
- Link和Route必须由BrowserRouter/HashRouter包裹,并且所有的只能包裹在一个路由器里面,所以一般都是直接在index.js中去包裹
<App />
<Link replace to="/about">about</Link> |
<Link to="/home"> home</Link>
<hr />
<div style={{background: 'red'}}>
<Switch>
<Route exact path="/" component={About}/>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/home"/>
</Switch>
</div>
- 标签体包含在props.children里
<MyLink a="a">About</MyLink>- 在MyLink组件中,
<NavLink {...this.props} />=> 其中props展开后包括children='About'
- 解决多级路由样式丢失的问题
<link ref="stylesheet" href="/css/bt.css"/>, 不要样式路径中的.<link ref="stylesheet" href="%PUBLIC_URL%/css/bt.css"/>- 用HashRouter替换BrowserRouter,#后面的不算在路由里(一般不用这种方式)
路由组件传参
params参数
<!-- 携带 -->
<Link to={`/about/detail/${msg.id}/${msg.title}`}>点击</Link>
<!-- 接收 -->
<Route path='/about/detail/:id/:title' component={Detail} />
<!-- 组件中获取 -->
this.props.match.params.id
this.props.match.params.title
search参数
<!-- 携带 -->
<Link to={`/about/detail/?id=${msg.id}&title=${msg.title}`}>点击</Link>
<!-- 无需接收 -->
<Route path='/about/detail' component={Detail} />
<!-- 组件中获取 -->
this.props.location.search => ?id=1&title=title1
处理得到的字符串
import qs from 'querystring'
const search = this.props.location.search
const {id, title} = qs.parse(search.slice(1))
console.log(id, title) // 1, title1
qs.stringify({name: 'sz', age: 18}) //name=sz&age=18
- querystring 是react自带的库
- querystring.parse:将一个字符串反序列化为一个对象
- querystring.stringify:将一个对象序列化为字符串
state参数(清缓存会丢数据)
<!-- 携带 -->
<Link to={{pathname: '/about/detail', state: {id: msg.id, title: msg.title}}}>点击</Link>
<!-- 无需接收 -->
<Route path='/about/detail' component={Detail} />
<!-- 组件中获取 -->
const {id, title} = this.props.location.state || {}
console.log(id, title) //1, title1
- BrowserRouter
- H5的history API,不兼容IE9以下版本
- 对路由state:没有影响,保存在history中
- HashRouter
- 使用URL的哈希值
- 对路由state:state数据会丢失
编程式导航
<link>和<NavLink>实现路由的跳转 是声明式导航- 通过js路由对象的方式叫做编程式导航 push replace go
- 路由对象只有被router处理过之后或者用WithRouter高阶组件处理过后才有
- 即只有路由组件才有this.props.history/location/match
//params
this.props.hsitory.replace(`/home/detail/${id}/${title}`)
this.props.hsitory.push(`/home/detail/${id}/${title}`)
//search
this.props.history.replace(`/home/detail?id=${id}&title={$title}`)
this.props.history.push(`/home/detail?id=${id}&title={$title}`)
//state
this.props.history.replace('/home/detail', {id, title})
this.props.history.push('/home/detail', {id, title})
//其他方法
this.props.history.go(-1)
this.props.history.goBack() //返回
this.props.history.goForward() //前进
WithRouter
- import {WithRouter} from 'react-router-dom'
- export default WithRouter(Header)
- WithRouter的返回值是一个新组件
redux
- 状态管理的js库
- 管理react中多个组件共享的状态
首先,用户发出 Action。
store.dispatch(action);
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有变化,Store 就会调用监听函数。
// 设置监听函数
store.subscribe(listener);
listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
store
- store就是保存数据的地方,整个应用只能有一个store
- Redux 提供
createStore这个函数,用来生成 Store createStore函数接受另一个函数作为参数,返回新生成的 Store 对象- store提供了3个方法
- store.getState()
- store.dispatch()
- store.subscribe()
state
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()拿到。
action
- 同步action,action的值是一个对象, {type: 'ACTIONNAME', data: data}
- 异步action,action的值是函数,因为函数可以开启一个异步任务
- reduce第一次调用,是store自动触发的
store.dispatch()
- store.dispatch()是 View 发出 Action 的唯一方法。
- store.getState()
- store.dispatch()
- store.subscribe()
reducer
- Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
- Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。- Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出
store.subscribe()
- Store 允许使用
store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数
// redux/store.js
import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reduce'
import thunk from 'redux-thunk'
// export default createStore(countReducer) 只有同步action
export default createStore(countReducer, applyMiddleware(thunk)) //有异步action时可以用thunk中间件
// redux/count_reduce.js
import { INCREMENT, DECREMENT } from './constants'
const initState = 99
function countReducer(preState = initState, action){
const { type, data } = action
switch(type){
case INCREMENT:
preState = preState + data
break
case DECREMENT:
preState = preState - data
break
default:
return preState
}
return preState
}
export default countReducer
// redux/constants.js 常量文件
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
// redux/count_action_creator.js 这里面定义的是store.dispatch调用的action
import { INCREMENT, DECREMENT } from './constants'
export const increment = data => ({type: INCREMENT, data})
export const decrement = data => ({type: DECREMENT, data})
export const incrementAsync = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data))
}, time)
}
}
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
import React, { Component } from 'react'
import store from './redux/store'
import {increment, decrement, incrementAsync} from './redux/count_action_creator'
export default class App extends Component {
state = {
count: 0
}
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
increment = () =>{
const { value } = this.selectedNumber
store.dispatch(increment(value*1))
}
decrement = () =>{
const { value } = this.selectedNumber
store.dispatch(decrement(value*1))
}
incrementIfOdd = () =>{
const { value } = this.selectedNumber
const count = store.getState()
if(count % 2 !== 0){
store.dispatch(increment(value*1))
}
}
incrementAsync = () =>{
const { value } = this.selectedNumber
// setTimeout(() => {
// store.dispatch(increment(value*1))
// }, 500)
store.dispatch(incrementAsync(value*1, 500))
}
render() {
return (
<div>
<h1>总数为:{ store.getState() }</h1>
<select ref={c => this.selectedNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={ this.increment }>加</button>
<button onClick={ this.decrement }>减</button>
<button onClick={ this.incrementIfOdd }>奇数加</button>
<button onClick={ this.incrementAsync }>异步加</button>
</div>
)
}
}
react-redux
- React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
- UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑
- 如果一个组件既有 UI 又有业务逻辑,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图
- React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成
UI组件
- 一般子组件是UI组件
- 不能使用任何redux的api
- 只负责页面的呈现,交互,不带有任何业务逻辑
- 没有状态,即没有this.state
- 所有数据都由参数(
this.props)提供 - 因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值
容器组件
- 一般父组件是容器组件
- 放在containers文件夹下
- 与redux通信,使用redux的api,将结果给UI组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
connect
- React-Redux 提供
connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList); //TodoList是UI组件,VisibleTodoList是connect生成的容器组件
- connect(mapStateToProps,mapDispatchToProps) 接收2个参数
- mapStateToProps
- 是一个函数,它接受
state作为参数,返回一个对象,传递给UI组件 - 执行后应该返回一个对象,里面的每一个键值对就是一个映射
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
- 是一个函数,它接受
- mapDispatchToProps
- 它可以是一个函数,也可以是一个对象
- 如果
mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数 - 如果
mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。(简易形式) - 返回操作状态的方法,传递给组件
- mapStateToProps
<Provider> 组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。- 一种解决方法是将
state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。 - React-Redux 提供
Provider组件,可以让容器组件拿到state - 使用
React-Router的项目,与其他项目没有不同之处,也是使用Provider在Router外面包一层,毕竟Provider的唯一功能就是传入store对象。
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
)
计数器实例
// redux 文件夹下的constants.js, count_action_creator.js, count_reduce.js, store.js 同上
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './redux/store'
//会自动给容器组件传递store
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>, document.getElementById('root'))
//用了react-redux不用再检测redux的变化
// store.subscribe(() => {
// ReactDOM.render(<App />, document.getElementById('root'))
// })
// App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
// import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{ /* <Count store={store} /> */}
<Count/> //优化后这里不用再传store
</div>
)
}
}
// containers/Count.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { increment, decrement, incrementAsync } from '../../redux/count_action_creator'
// 将UI组件直接合并在容器组件中,不用再暴露出去
class Count extends Component {
increment = () =>{
const { value } = this.selectedNumber
this.props.add(value*1)
}
decrement = () =>{
const { value } = this.selectedNumber
this.props.dec(value*1)
}
incrementIfOdd = () =>{
const { value } = this.selectedNumber
const { count } = this.props
if(count % 2 !== 0){
this.props.add(value*1)
}
}
incrementAsync = () =>{
const { value } = this.selectedNumber
this.props.addAsync(value*1)
}
render() {
console.log(this.props)
return (
<div>
<h1>总数为:{ this.props.count }</h1>
<select ref={c => this.selectedNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={ this.increment }>加</button>
<button onClick={ this.decrement }>减</button>
<button onClick={ this.incrementIfOdd }>奇数加</button>
<button onClick={ this.incrementAsync }>异步加</button>
</div>
)
}
}
//这里connect第二个参数是一个对象,dispatch会自动分发action
export default connect(
state => ({count: state}),
{
add: increment,
dec: decrement,
addAsync: incrementAsync
}
)(Count)
合并reducer
src 目录结构
- containers
- Count
- index.jsx
- Person
- index.jsx
- Count
- redux
- actions
- count.js
- person.js
- reducers
- count.js
- person.js
- constants.js
- store.js
- actions
- App.jsx
- index.js
// redux/store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'
const allReducers = combineReducers({
count: countReducer,
person: personReducer
})
export default createStore(allReducers, applyMiddleware(thunk))
// redux/contants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
// redux/reducers/count.js
import { INCREMENT, DECREMENT } from '../constants'
const initState = 99
function countReducer(preState = initState, action){
const { type, data } = action
switch(type){
case INCREMENT:
return preState = preState + data
case DECREMENT:
return preState = preState - data
default:
return preState
}
}
export default countReducer
// redux/reducers/person.js
import { ADD_PERSON } from '../constants'
const persons = [
{id: '1', name: 'zs', age: 18},
{id: '2', name: 'ls', age: 19},
]
function personReducer(preState = persons, action) {
const { type, data } = action
switch(type) {
case ADD_PERSON:
return [data, ...preState]
default:
return preState
}
}
export default personReducer
// redux/actions/count.js
import { INCREMENT, DECREMENT } from '../constants'
export const increment = data => ({type: INCREMENT, data})
export const decrement = data => ({type: DECREMENT, data})
export const incrementAsync = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data))
}, 500)
}
}
// redux/actions/person.js
import { ADD_PERSON } from '../constants'
export const addPerson = data => ({type: ADD_PERSON, data})
// containers/Count/index.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { increment, decrement, incrementAsync } from '../../redux/actions/count'
class Count extends Component {
increment = () =>{
const { value } = this.selectedNumber
this.props.add(value*1)
}
decrement = () =>{
const { value } = this.selectedNumber
this.props.dec(value*1)
}
incrementIfOdd = () =>{
const { value } = this.selectedNumber
const { count } = this.props
if(count % 2 !== 0){
this.props.add(value*1)
}
}
incrementAsync = () =>{
const { value } = this.selectedNumber
this.props.addAsync(value*1)
}
render() {
return (
<div>
<h1>这是Count组件,Person组件总共有{ this.props.personCount }人</h1>
<h3>总数为:{ this.props.count }</h3>
<select ref={c => this.selectedNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={ this.increment }>加</button>
<button onClick={ this.decrement }>减</button>
<button onClick={ this.incrementIfOdd }>奇数加</button>
<button onClick={ this.incrementAsync }>异步加</button>
</div>
)
}
}
export default connect(
state => ({count: state.count, personCount: state.person.length}),
{
add: increment,
dec: decrement,
addAsync: incrementAsync
}
)(Count)
// containers/Person/index.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { nanoid } from 'nanoid'
import { addPerson } from '../../redux/actions/person'
class Person extends Component {
addPerson = () => {
const name = this.nameNode.value
const age = this.ageNode.value*1
const person = {
id: nanoid(),
name,
age
}
this.props.addPerson(person)
this.nameNode.value = ''
this.ageNode.value = ''
}
render() {
return (
<div>
<h1>这是Person组件, Count组件的结果为{this.props.count}</h1>
<div>
<input type="text" ref={c => this.nameNode = c} placeholder='请输入名字'/>
<input type="text" ref={c => this.ageNode = c} placeholder='请输入年龄'/>
<button onClick={ this.addPerson }>添加</button>
</div>
{
this.props.persons.map(item => {
return <li key={item.id}>name: {item.name} -- age: {item.age}</li>
})
}
</div>
)
}
}
export default connect(
state => ({persons: state.person, count: state.count}),
{
addPerson
}
)(Person)
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './redux/store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'))
// store.subscribe(() => {
// ReactDOM.render(<App />, document.getElementById('root'))
// })
// App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'
export default class App extends Component {
render() {
return (
<div>
<Count/>
<hr/>
<Person />
</div>
)
}
}
redux的reduce必须是纯函数
- 同样的输入,得到同样的输出
- 不得改写参数数据
- 不会产生任何副作用,如网络请求,输入和输出设备
- 不能调用Date.now()或Math.random()等不纯的方法
其他
setState
- 同步方法,但引起的后续动作是异步的
// 1. 对象式
setState({}, () => {
// 第二个参数是一个回调函数,可选。在render后调用
})
// 2.函数式: 依赖原状态
setState((state, props) => {
return {count: state.count+1}
}, () => {})
懒加载 lazy
- lazy是一个函数,需要传入一个函数
import {lazy, Suspense} from 'react'
const Home = lazy(() => import('./Home'))
// 可以抽成Loading组件,但是不能用懒加载的方式来加载
<Suspense fallback={<h1>Loading...</h1>}>
<Route path="xxx" component={Home} />
...
</Suspense>
函数式组件的勾子 Hooks
- react.useState(initValue) 模拟state
- useState 中的函数只会在初始化的时候执行一次。
- 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
import {useState} from 'react'
export default function App(props){
const [count, setCount] = useState(0)
return (
<div>
<h1>当前次数:{count}</h1>
<button onClick={() => setCount(count+1)}>点击+1</button>
<button onClick={() => setCount(count => count + 1)}>点击+1</button>
</div>
)
}
当设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。而当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setTimeout(() => {
setCount(count + 1)
}, 3000);
}
function handleClickFn() {
setTimeout(() => {
setCount((prevCount) => {
return prevCount + 1
})
}, 3000);
}
return (
<>
Count: {count}
<button onClick={handleClick}>+</button>
<button onClick={handleClickFn}>+</button>
</>
);
}
- useEffect(): 模拟生命周期勾子
import React, { useEffect } from 'react';
function Welcome(props) {
useEffect(() => {
document.title = '加载完成';
});
return <h1>Hello, {props.name}</h1>;
}
- 组件每渲染一次,useEffect()函数就自动执行一次。组件首次在网页 DOM 加载后,useEffect()函数也会执行。
- useEffect() 的第二个参数: 不希望
useEffect()每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。
function Welcome(props) {
useEffect(() => {
document.title = `Hello, ${props.name}`;
}, [props.name]);
return <h1>Hello, {props.name}</h1>;
}
上面例子中,useEffect()的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name)。只有该变量发生变化时,副效应函数才会执行。
如果没有第二个参数,则监测所有的state。如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行
- useEffect()的用途
- 获取数据(data fetching)
- 事件监听或订阅(setting up a subscription)
- 改变 DOM(changing the DOM)
- 输出日志(logging)
- useEffect()的返回值
useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);
- useRef: 模拟ref
import {useRef} from 'react'
const myRef = useRef()
<input ref={myRef}/>
获取值: myRef.current.value
import {useEffect, useRef} from 'react'
export default function App(props){
const myRef = useRef()
useEffect(() => {
document.title = 'test'
})
const changeInput = () => {
console.log(myRef)
const inputValue = myRef.current.value
}
return (
<div>
<h1>Test...{props.a}</h1>
<input type="text" ref={myRef} onChange={changeInput} />
</div>
)
}
Fragment
- 替代div,少一层结构
- 只允许写key属性值(遍历时)
- 也可以直接用空标签
<></>,但是空标签不允许写任何属性
Context
- 用于父与孙之间的通信,越级通信