基于react的todolist实现-划分组件
多层次组件通讯
组件通讯:
1、父子通讯:props;
父组件操作:定义属性并传递数据
子组件操作:函数组件:函数的第一个参数、类组件:this.props
2、子父通讯:把父组件的方法传到子组件去执行并传递参数;
父组件操作:定义方法并通过props传入子组件 (数据在哪里就把方法定义在哪里)
子组件操作:执行方法并传递参数
3、兄弟通讯:状态提升
把状态放到兄弟组件的共同的父级
4、多层次组件通讯;
逐层传递
:一层一层传递(比较繁琐)Context
1、创建Context ;
2、父组件操作:Provider 共享数据 其中的 value;
3、子组件操作:接收数据 ;分为函数组件 :Consumer / 类组件 : Consumer 和 this.context
具体说说多层次组件通讯
1、props 逐层传递
从上到下,所有的组件都要帮助传递这个 props 到目标位置
props 逐层传递缺点:
- 操作繁琐
- 难以维护
index.js
import React from 'react'
import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'
class TodoList extends React.Component {
constructor() {
// 必须写这句 继承React.Component
super();
this.state = {
datalist: [{
id: 1,
target: '按时吃饭',
done: false,
addtime: Date.now()
}, {
id: 2,
target: '早睡早起',
done: false,
addtime: Date.now()
}]
}
this.addItem = this.addItem.bind(this)
this.removeItem =this.removeItem.bind(this)
this.completeItem = this.completeItem.bind(this)
}
addItem(target){
const newData = {
id:parseInt(Math.random()*10000),
target,
done:false,
addtime:Date.now()
}
// 如果上面没有bind改变this指向 则打印不出数据并报错
console.log(this.state)
this.setState({
datalist:[newData, ...this.state.datalist]
})
}
removeItem(id){
this.setState({
datalist:this.state.datalist.filter(item => item.id !== id)
})
}
completeItem(id){
this.setState({
datalist:this.state.datalist.map(item => {
if(item.id === id){
item.done = true
}
return item
})
})
}
render(){
const {datalist} = this.state
const total=datalist.length
const doneLength = datalist.filter(item => item.done).length
const undoneLength = datalist.filter(item => !item.done).length
return (
<div>
{/* 定义方法并通过props传入子组件 */}
<TodoFrom addItem={this.addItem}></TodoFrom>
<TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
<TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
</div>
)
}
}
export default TodoList
TodoForm.js
import React from 'react'
// 类组件
class TodoForm extends React.Component{
constructor(){
super()
this.state={
keyword:''
}
this.changeKeyword=this.changeKeyword.bind(this)
this.add=this.add.bind(this)
}
changeKeyword(e){
this.setState({
keyword:e.target.value
})
}
add(){
// 类组件 子到父通讯 通过 this.props 执行方法 并传递参数
this.props.addItem(this.state.keyword)
this.setState({
keyword:''
})
this.keyword.focus()
}
render(){
return (
<div>
<input type="text" value={this.state.keyword} onChange={this.changeKeyword} ref={el=>this.keyword=el}/>
<button onClick={this.add}>添加</button>
</div>
)
}
}
export default TodoForm
TodoContent.js
import React from 'react'
import TodoItem from './TodoItem'
// 在函数参数中,接收父组件通讯传过来的参数
function TodoContent({datalist,removeItem,completeItem}){
// 通过传参 接收传递的数据
// console.log('TodoItem=',datalist)
return (
<table style={{width:'100%',textAlign:'center'}}>
<thead>
<tr>
<th>序号</th>
<th>待办事项</th>
<th>是否完成</th>
<th>操作</th>
{/* {<th>添加时间</th>} */}
</tr>
</thead>
<tbody>
{
// 必须加一个key值
datalist.map((item,idx)=>(
<TodoItem
index={idx}
data={item}
key={item.id}
removeItem={removeItem}
completeItem={completeItem}
/>
)
)
}
</tbody>
</table>
)
}
export default TodoContent
TodoItem.js
import React from 'react'
// 函数组件 仅仅为了展示 没有状态改变
// 在函数参数中,接收父组件通讯传过来的参数
function TodoItem({data,index,removeItem,completeItem}){
console.log(1)
console.log('TodoItem=',data,index)
return (
<tr>
<td>{index+1}</td>
<td>{data.target}</td>
<td>{data.done?'是':'否'}</td>
<td>
<button onClick={completeItem.bind(null,data.id)}>完成</button>
<button onClick={removeItem.bind(null,data.id)}>删除</button>
</td>
</tr>
)
}
export default TodoItem
TodoState.js
import React from 'react'
// 在函数参数中,接收父组件通讯传过来的参数
function TodoStatue({total,doneLength,undoneLength}){
// console.log('TodoState=',total,doneLength,undoneLength)
return (
<div>
总数:{total},
完成:{doneLength},
未完成:{undoneLength}
</div>
)
}
export default TodoStatue
2、Context 组件共享
所谓 context,就是上下文环境,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都能直接访问这个状态,实现步骤如下:
1、创建Context
const MyContext = React.createContext(defaultValue)
或者新建一个js文件专门存放context
import React from 'react';
// 1.创建Context
const defaultData = {username:'fqniu'}
const myContext = React.createContext(defaultData);
export default myContext
2、父组件操作:Provider共享数据
let data = {username:'fqniu'}
<MyContext.Provider value={data}>
<App/>
</MyContext.Provider>
组件
App
下的所有子组件
都能获取到data
数据,如父组件未设置 Provider,子组件接收时得到 defaultValue 的值
其中value = { 里面是共享的数据 }
3、子组件操作:接收数据
- 函数组件 :Consumer
- 类组件:Consumer、this.context
Consumer: 函数组件和类组件都可以
<MyContext.Consumer>
{(value) => {
// 回调函数中获取value值
}}
</MyContext.Consumer>
contextType: 只适用于类组件,通过`this.context`获取 , 设置静态属性contextType
SubComponent.contextType = MyContext;
this.context.username; //fqniu
而这里的TodoItem 组件 如果逐层传递的话 ,比较繁琐,所以使用Context 传递
myContext.js
import React from 'react'
// 1、创建Context
const defaultData={username:'fqniu'}
const myContext = React.createContext(defaultData)
export default myContext
index.js
import React from 'react'
import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'
// 引入Context
import myContext from './myContext'
class TodoList extends React.Component {
constructor() {
// 必须写这句 继承React.Component
super();
this.state = {
datalist: [{
id: 1,
target: '按时吃饭',
done: false,
addtime: Date.now()
}, {
id: 2,
target: '早睡早起',
done: false,
addtime: Date.now()
}]
}
this.addItem = this.addItem.bind(this)
this.removeItem =this.removeItem.bind(this)
this.completeItem = this.completeItem.bind(this)
}
addItem(target){
const newData = {
id:parseInt(Math.random()*10000),
target,
done:false,
addtime:Date.now()
}
// 如果上面没有bind改变this指向 则打印不出数据并报错
console.log(this.state)
this.setState({
datalist:[newData, ...this.state.datalist]
})
}
removeItem(id){
this.setState({
datalist:this.state.datalist.filter(item => item.id !== id)
})
}
completeItem(id){
this.setState({
datalist:this.state.datalist.map(item => {
if(item.id === id){
item.done = true
}
return item
})
})
}
render(){
const {datalist} = this.state
const total=datalist.length
const doneLength = datalist.filter(item => item.done).length
const undoneLength = datalist.filter(item => !item.done).length
return (
<div>
{/* 定义方法并通过props传入子组件
<TodoFrom addItem={this.addItem}></TodoFrom>
<TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
<TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState> */}
{/* Context传递 */}
<myContext.Provider value={{remove:this.removeItem, complete:this.completeItem}}>
<TodoFrom addItem={this.addItem}></TodoFrom>
<TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
<TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
</myContext.Provider>
</div>
)
}
}
export default TodoList
TodoItem.js
import React from 'react'
import myContext from './myContext'
// 函数组件 仅仅为了展示 没有状态改变
// 在函数参数中,接收组件通讯传过来的参数
function TodoItem({data,index,removeItem,completeItem}){
console.log(1)
console.log('TodoItem=',data,index)
return (
<tr>
<td>{index+1}</td>
<td>{data.target}</td>
<td>{data.done?'是':'否'}</td>
<td>
{/* 逐层传递
<button onClick={completeItem.bind(null,data.id)}>完成</button>
<button onClick={removeItem.bind(null,data.id)}>删除</button> */}
{/* Context传递 */}
<myContext.Consumer>
{
({remove,complete})=>{
return (
<React.Fragment>
<button onClick={complete.bind(null,data.id)}>完成</button>
<button onClick={remove.bind(null,data.id)}>删除</button>
</React.Fragment>
)
}
}
</myContext.Consumer>
</td>
</tr>
)
}
export default TodoItem
注意点
1、
<React.Fragment></React.Fragment>
当嵌套标签时,可以简写<></>
2、如父组件未设置Provider
,子组件接收时得到defaultValue
的值
修改类组件中class的代码
但是不是感觉那个类组件 class 里面代码要 bind 对自定义方法中this的指向
、 还要super()
,还有constructor ()
,感觉不怎么友好,这时候推荐一个插件来解决此类的问题 : @babel/plugin-proposal-class-properties
,已经配置在webpack.config.js
中,不清楚的可以看我上一个博客内容 :基于webpack的react环境配置 中插件配置
index.js
import React from 'react'
import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'
// 引入Context
import myContext from './myContext'
class TodoList extends React.Component {
// constructor() {
// // 必须写这句 继承React.Component
// super();
// this.state = {
// datalist: [{
// id: 1,
// target: '按时吃饭',
// done: false,
// addtime: Date.now()
// }, {
// id: 2,
// target: '早睡早起',
// done: false,
// addtime: Date.now()
// }]
// }
// this.addItem = this.addItem.bind(this)
// this.removeItem =this.removeItem.bind(this)
// this.completeItem = this.completeItem.bind(this)
// }
// 添加class插件后写法:
// 静态属性,就是给类添加属性,如果有静态属性的写法如下:
static propTypes={}
static defaultProps ={}
// 这样就不用 写construct 和 super
state = {
datalist: [{
id: 1,
target: '按时吃饭',
done: false,
addtime: Date.now()
}, {
id: 2,
target: '早睡早起',
done: false,
addtime: Date.now()
}]
}
// addItem(target){
// const newData = {
// id:parseInt(Math.random()*10000),
// target,
// done:false,
// addtime:Date.now()
// }
// // 如果上面没有bind改变this指向 则打印不出数据并报错
// console.log(this.state)
// this.setState({
// datalist:[newData, ...this.state.datalist]
// })
// }
// removeItem(id){
// this.setState({
// datalist:this.state.datalist.filter(item => item.id !== id)
// })
// }
// completeItem(id){
// this.setState({
// datalist:this.state.datalist.map(item => {
// if(item.id === id){
// item.done = true
// }
// return item
// })
// })
// }
// 箭头函数写法 前提是有安装一个插件 class的
// 注意:箭头函数的话 无this 指向外面的实例,就不用bind去改变this指向
addItem = (target) => {
const newData = {
id:parseInt(Math.random()*10000),
target,
done:false,
addtime:Date.now()
}
// 如果上面没有bind改变this指向 则打印不出数据并报错
console.log(this.state)
this.setState({
datalist:[newData, ...this.state.datalist]
})
}
removeItem = (id) => {
this.setState({
datalist:this.state.datalist.filter(item => item.id !== id)
})
}
completeItem = (id) => {
this.setState({
datalist:this.state.datalist.map(item => {
if(item.id === id){
item.done = true
}
return item
})
})
}
render(){
const {datalist} = this.state
const total=datalist.length
const doneLength = datalist.filter(item => item.done).length
const undoneLength = datalist.filter(item => !item.done).length
return (
<div>
{/* 定义方法并通过props传入子组件
<TodoFrom addItem={this.addItem}></TodoFrom>
<TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
<TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState> */}
{/* Context传递 */}
<myContext.Provider value={{remove:this.removeItem, complete:this.completeItem}}>
<TodoFrom addItem={this.addItem}></TodoFrom>
<TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
<TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
</myContext.Provider>
</div>
)
}
}
export default TodoList
TodoForm.js
import React from 'react'
// 引入Context
import myContext from './myContext'
// 类组件
class TodoForm extends React.Component{
// constructor(){
// super()
// this.state={
// keyword:''
// }
// this.changeKeyword=this.changeKeyword.bind(this)
// this.add=this.add.bind(this)
// }
// 添加静态属性:等效于TodoForm.xxx = xxx
// 注意:ES6 支持静态方法,但是不支持静态属性
static contextType = myContext
// static getData(){}
// 添加实例属性:等效于this.state = {}
// 添加class插件后写法:
state={
keyword:''
}
// changeKeyword(e){
// this.setState({
// keyword:e.target.value
// })
// }
// add(){
// // 执行方法并传递参数
// this.props.addItem(this.state.keyword)
// this.setState({
// keyword:''
// })
// this.keyword.focus()
// }
// changeKeyword(e){
// this.setState({
// keyword:e.target.value
// })
// }
// 箭头函数写法 前提是有安装一个插件 class的
changeKeyword = (e) => {
this.setState({
keyword:e.target.value
})
}
add = () => {
// 子组件:执行方法并传递参数
this.props.addItem(this.state.keyword)
this.setState({
keyword:''
})
this.keyword.focus()
}
changeKeyword = (e) => {
this.setState({
keyword:e.target.value
})
}
render(){
return (
<div>
<input type="text" value={this.state.keyword} onChange={this.changeKeyword} ref={el=>this.keyword=el}/>
<button onClick={this.add}>添加</button>
</div>
)
}
}
// 添加静态属性
// TodoFrom.contextType = myContext
export default TodoForm
注意点
1、箭头函数的话 无this 指向外面的实例,就不用bind去改变this指向
2、ES6 支持静态方法,但是不支持静态属性 ,这里能用静态属性的原因是关于自定义方法使用箭头函数的插件@babel/plugin-proposal-class-properties
react基础
1、React介绍
React 官网地址:http://facebook.github.io/react/
-
介绍
React 是一个视图层的框架,起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
-
特点
-
声明式设计与函数式编程
声明式编程与命令式编程。
- 声明式:我要做什么,注重结果
- 命令式:我要怎么做,注重过程
- 函数式编程:利用函数参数、返回值等去实现功能的编程方式
-
高性能
React 通过对 DOM 的模拟(Virtual DOM),并配合 diff 算法,最大限度地减少与 DOM 的交互,从而提升性能。
-
组件化开发
通过 React 构建组件,使得代码更加容易得到复用,能够高效率的应用在大项目的开发中。
-
单向响应的数据流
React 中的数据是单向自顶向下传递的,父组件数据的更新会自动传递到子组件,但子组件的数据更新不会影响到父组件,也不能在子组件修改父组件传入的数据
-
JSX 扩展
JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
-
2. 使用 React
-
script 标签引入
-
react.js React 的核心库
-
react-dom.js 提供与 DOM 相关的功能
-
browser.js babel 针对于浏览器环境的版本,可将 JSX、ES6+等语法编译成浏览器支持的代码
在浏览器中使用 Babel 来编译 JSX 为实时编译, 效率是非常低的,一般只用于演示
-
-
使用
-
创建虚拟节点:
React.createElement(type,props,children)
- type: 节点名称
- props: 节点属性
- children: 节点内容
-
渲染节点:
ReactDOM.render(VNode,target)
是 ReactDOM 的最基本方法,用于将内容渲染到指定节点中
- VNode:虚拟节点或 React 组件
- target:挂载点,必须为元素节点
<script> ReactDOM.render( React.createElement("div", { className: "container" }, "创建虚拟节点"), document.getElementById("app") ); </script>
-
3. JSX 语法
一种特殊的 js 语法,是 ECMAScript 的扩展,可以让我们在 js 代码中使用 html 标签来编写结构,避免繁琐的React.createElement()
操作
PS:JSX 是
React.createElement()
的语法糖,虽然看起来像 HTML,但严格意义上来说它还是 JS,这种特殊的 JS 浏览器无法识别,所以需要 babel 进行编辑,编辑后它最终会转化成React.createElement()
去创建 js 对象。
<script type="text/babel">
ReactDOM.render(
React.createElement("div", { className: "container" }, "创建虚拟节点"),
document.getElementById("app")
);
</script>
注意:使用 JSX 需要遵循以下规则
-
因为 Javascript 代码与 JSX 代码并不兼容,凡是使用 JSX 的 script 标签都需要加上
type="text/babel"
-
在 jsx 代码中,同为 js 关键字的 html 属性不能直接使用
class -> className
,for -> htmlFor
-
属性使用驼峰
tabindex -> tabIndex
autofocus -> autoFocus
onkeydown -> onKeyDown
- …
-
必须结束标签
<input type="text" /> <img src="logo.png" />
-
style 属性的值接收一个对象,css 的属性必须为驼峰写法
<div style={{backgroundColor:"#f60"}}
-
花括号
{}
内为 js 表达式,不允许出现var
,let
,const
等关键字 -
使用 js 语法注释(如{
/*注释内容*/
},//注释内容
)
4. React 组件
所谓组件,即封装起来的具有独立功能的 UI 部件,用来组合成更高级东西的物件,通俗来讲,就是创建一个元素,组件有以下规范:
- 组件名必须大写开头
- 只能包含一个顶层标签
PS:在开发过程中,要善于观察和抽象。尤其是在项目前期,不要着急写代码,一定要观察项目的原型图或者设计稿,弄懂哪些部分是可以拆分成复用的公共组件的。这样做能让你后面的工作,事半功倍
组件的类型与定义
- 函数组件
- 类组件
搞懂组件的定义与使用后,更重要的是要学会根据需求组合或拆分更大或更细的组件
函数组件(无状态组件、UI 组件)
是一个纯函数,只用组件展示,组件只负责根据外部传入的 props 来展示,书写更简洁,执行效率更高(推荐)
//定义
function MyComponent(){
return <h1>函数组件</h1>
}
//使用
<MyComponent />
<MyComponent></MyComponent>
类组件(状态组件、容器组件)
类组件有更丰富的特性,如:state 状态、生命周期、this 等
// 定义
class About extends React.Component {
render() {
return <div className="box">类组件</div>;
}
}
// 使用
<About />;
5、react 组件的数据挂载方式
1. state:适用于类组件
React 类组件拥有自己的状态 state,state 状态改变时自动刷新组件(执行组件中的 render 方法)
- 初始 state
class MyComponent extends React.Component {
constructor() {
super(); // 这行代码不能少
this.state = {
num: 1,
};
}
}
-
修改状态:
setState()
-
直接修改:
setState(nextState[,callback])
- nextState: 将要设置的新状态,该状态会和当前的 state 合并
- callback: 可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用。
this.setState({ num: 10, });
-
依赖上次
setState()
的结果:setState(fn [,callback])
- fn(prevState)
this.setState((prevState) => { return { num: prevState.num + 1 }; });
-
-
注意事项
-
setState()是异步的
PS:调用 setState()并不会马上修改 state。而是进入到一个更新队列里面,所以不能在组件内部通过
this.state.xx=xx
直接修改状态,因为修改后会被队列中的 setState()替换(如下两次输出都为 false) -
多次
setState()
会自动合并React 内部自动进行 state 的对比,得到最终结果后才渲染视图,所以并不需要担心多次进行
setState()
会带来性能问题
console.log(this.state.num); //1 this.setState({ num: 10, }); console.log(this.state.num); //1
setState()
会自动刷新组件,也可以利用forceUpdate()
进行手动强制刷新
-
2. props:类组件与函数组件都适用
- 函数组件:通过函数的第一个参数访问
- 类组件:通过
this.props
访问
- 普通属性
<MyComponent data={100} />
-
Render Props
使用一个值为函数的 prop 共享代码的简单技术(类似于 Vue 中的作用域插槽)
// Vue的实现 // <mycomponent> // <template v-slot:header="data"> // <header>头部标题{{data}}</header> // </template> // </mycomponent> <MyComponent render={(data) => <header>头部标题{data}</header>} />
-
children
<Button>点我有惊喜</Button>
// Button组件内获取通过props.children获取"点我有惊喜"
function Button(){
return <button>{props.children}<button>
}
props 类型校验
给组件设置静态属性 propTypes 来设置组件各个属性的类型检查器, 用于限制传入属性的数据类型,在编写组件时比较有用
-
React 内置数据类型检查器
PropTypes
在 React 16 版本之后, PropTypes 从 react 包 分离到了 prop-types 包中
需要npm i prop-types
安装
import PropTypes from "prop-types";
MyComponent.propTypes = {
name: PropTypes.string,
};
- 自定义属性校验器
MyComponent.propTypes = {
//自定义验证规则
age: (props, propName, comName) => {
if (props[propName] < 18) {
return new Error(propName + "必须大于等于18岁");
}
},
};
3. 条件渲染:三元运算
{
login ?
<button>退出</button>
:
<button>登录</button>
}
4. 列表循环
使用数组的 map()
方法来创建列表,可配合filter()
进行过滤操作
-
key
react利用key来区分组件的
- key相同,表示同一个组件,react不会重新销毁创建组件实例,只可能更新;
- key不同,react会销毁已有的组件实例,重新创建组件新的实例
- key应该是稳定唯一的,尽量不要用数组的索引index作为key(排序或添加时索引值会改变)
5. 事件处理
事件名称采用驼峰式写法(如:onClick,onKeyDown)
event对象与传参
事件处理函数的最后一个参数,保存事件发生时的信息
//普通使用
clickHandle(e){
console.log(e);
}
<button onClick={this.clickHandle}>按钮</button>
// bind方式
clickHandle(num1,num2,e){
console.log(num1,num2,e);
}
<button onClick={this.clickHandle.bind(this,10,20)}>按钮</button>
// 箭头函数
clickHandle(e,num){
console.log(e,num);
}
<button onClick={e=>this.clickHandle(e,10)}>按钮</button>
事件处理函数中的this指向
默认情况下,事件处理函数没有this指向(值为undefined),可通过以下方式改变this指向
clickHandle(){
console.log(this);//undefined
}
<button onClick={this.clickHandle}>按钮</button>
-
bind方法
- 初始化时bind (推荐)
- 执行时bind
class MyComponent extends React.Component{ constructor(props){ super(props); this.clickHandle = this.clickHandle.bind(this) } clickHandle(){ } }
-
使用箭头函数
- 定义时使用箭头函数
class MyComponent extends React.Component{ // 需要插件支持:@babel/plugin-proposal-class-properties clickHandle = ()=>{ } }
- render中使用箭头函数
<button onClick={()=>{}}>按钮</button>
6. refs
受控组件与非受控组件
-
应用位置
- 应用在元素节点上:对节点的引用
- 应用在组件上:对组件实例的引用
函数组件不可使用ref
-
适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方 DOM 库
-
设置方式
-
React.createRef()
-
回调 Refs (推荐)
ref={el=>this.myRef=el}
-
// React.createRef()
this.btnSave = React.createRef();
<button ref={this.btnSave}>保存</button>
//获取节点
this.btnSave.current
//回调 Refs
<button ref={el => {this.btnSave = el}}>保存</button>
// 获取节点
this.btnSave
7. 显示html内容
// htmlString为带html标签的内容
<div dangerouslySetInnerHTML={{ __html: htmlString}}></div>
6、组件通讯
1. 父子通讯:props
2. 兄弟组件通讯:状态提升
把状态放到两个组件共同的父级
3. 多层次组件通讯
-
props 逐层传递
从上到下,所有的组件都要帮助传递这个 props 到目标位置
- 缺点:
- 操作繁琐
- 难以维护
- 缺点:
-
context 组件共享
所谓 context,就是上下文环境,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都能直接访问这个状态,实现步骤如下:
-
创建 Context:
let defaultValue = { username: "laoxie" }; let MyContext = React.createContext(defaultValue);
-
父组件 Provider
let data = {username:'jingjing'} <MyContext.Provider value={data}> <App/> </MyContext.Provider>
组件
App
下的所有子组件都能获取到data
数据,如父组件未设置 Provider,子组件接收时得到 defaultValue 的值 -
子组件接收
- contextType
只适用于类组件,通过
this.context
获取SubComponent.contextType = MyContext; this.context.username; //jingjing
-
-
Consumer
<MyContext.Consumer> {(value) => { // 回调函数中获取value值 }} </MyContext.Consumer>
7、高阶组件 HOC(High Order Component)
理解 HOC
- 高阶组件是一个纯函数
- 高阶组件的参数为组件,返回值为新组件
- 高阶组件是一种设计模式,类似于装饰器模式
定义HOC
-
定义方式一: 属性代理
作用: 提取公共部分,向下传输 props
//utils/withStorage.js
import React, { Component } from "react";
const withStorage = (WrappedComponent) => {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem("data");
this.setState({ data });
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
};
export default withStorage;
-
定义方式一: 反向继承
作用: 拦截生命周期、state、渲染过程等
import React, { Component } from "react";
const withStorage = (WrappedComponent) => {
return class extends WrappedComponent {
componentWillMount() {
let data = localStorage.getItem("data");
this.setState({ data });
// 调用父类生命周期函数,使之不被覆盖
super.componentWillMount();
}
render() {
// 调用父类render方法实现渲染
return super.render();
}
};
};
export default withStorage;
在组件中使用 HOC
//components/Home.js
import React, { Component } from "react";
import withStorage from "../utils/withStorage";
class Home extends Component {
render() {
//通过高阶组件可以直接获取data
return <h2>{this.props.data}</h2>;
}
}
export default withStorage(Home);
- ES7 装饰器写法:
@
import React, { Component } from "react";
import withStorage from ".../utils/withStorage";
@withStorage
class Home extends Component {
render() {
return <h2>{this.props.data}</h2>;
}
}
export default Home;
PS:目前浏览器不支持 ES7 装饰器,需安装
@babel/plugin-proposal-decorators
插件
8、组件生命周期
组件的生命周期分成四个状态:
- Initial: 初始化阶段
- Mounting:挂载阶段
- Updating:更新阶段
- Unmounting:卸载阶段
- 特殊生命周期函数
补充一点:
场景如下:假如在 componentDidMount()
里面发ajax请求的话,如果更新数据后,不会自己再发请求了,因为页面加载只执行一次 componentDidMount()
,因为更新在componentDidUpdate()
里面;
钩子函数
- componentWillMount (不推荐,V17.x版本中将移除)
在组件被渲染到页面上之前执行
-
componentDidMount
组件被渲染到页面上后立马执行
- 这个时候是做如下操作的好时机:
- 某些依赖组件 DOM 节点的操作
- 发起ajax请求
- 设置 setInterval、setTimeout 等计时器操作
- 读取本地存储数据
- 这个时候是做如下操作的好时机:
-
componentWillUpdate(nextProps, nextState) (不推荐,V17.x版本中将移除)
在初始化时不会被调用,可能在以下两种情况下被调用:
- 当组件 shouldComponentUpdate 返回 true 且接收到新的props或者state但还没有render时被调用
- 调用 forceUpdate 时将触发此函数
-
componentDidUpdate(prevProps, prevState)
在组件完成更新后立即调用。在初始化时不会被调用。
-
在此处是做这些事情的好时机:
- 执行依赖新 DOM 节点的操作。
- 依据新的属性发起新的ajax请求。
注意:一定要在确认属性变化后再发起ajax请求,否则极有可能进入死循环:DidUpdate -> ajax -> changeState -> DidUpdate -> …)
-
-
componentWillUnmount
在组件从 DOM 中移除之前立刻被调用。
- 此处最适合做以下操作
为了性能优化,防止内存泄漏
- 清除定时器
- 终止ajax请求
- 此处最适合做以下操作
-
componentWillReceiveProps(nextProps) (不推荐,V17.x版本中将移除)
该方法在以下两种情况下被调用:
- 组件接收到了新的props属性。新的属性会通过 nextProps 获取到。
- 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
-
shouldComponentUpdate(nextProps, nextState)
在props改变或state改变时被调用,必须返回true或false来决定给是否重新渲染组件
- 在初始化时或者使用forceUpdate时不被调用。
- 一般用于性能优化
//在render函数调用前判断:如果前后state中num不变,通过return false阻止render调用 shouldComponentUpdate(nextProps,nextState){ if(nextState.num == this.state.num){ return false } }
PS:这是一个询问式的生命周期函数,如果返回true,组件将触发重新渲染过程,如果返回false 组件将不会触发重新渲染。因此,合理地利用该函数可以一定程度节省开销,提高系统的性能
钩子函数执行顺序如下:
1、进入页面:
componentWillMount
——> render
——> componentDidMount
2、更新数据后:
shouldComponetUpdate
——>componentWillMount
——> render
——>componentDidMount
3、切换组件后:
componentWillUnmount
react 中性能优化的方案
- shouldComponentUpdate
- PureComponent
与React.Component
的区别是PureComponent
内部帮我们做了shouldComponentUpdate
的简单判断,让state和props不变的情况下自动return false
,从而实现性能优化。
注意:用React.PureComponent()
,但是里面不能再写shouldComponentUpdate()
会报错。
补充如下:
import React from 'react'
class Lifecycle extends React.Component{
// 如果用React.PureComponent 是不用加 shouldComponentUpdate,加了会报错
// 内部已经做了简单的处理了优化,后面用这种比较多
// class Lifecycle extends React.PureComponent{
constructor(){
super()
this.state={
qty:0
}
}
// 生命周期函数
// 1、挂载阶段
componentWillMount(){
console.log('componentWillMount')
}
componentDidMount(){
console.log('componentDidMount')
}
// 2、更新阶段
componentWillUpdate(nextProps, nextState){
// this.props, this.state.qty:1 / nextState.qty:2
console.log('componentWillUpdate');
}
componentDidUpdate(nextProps, nextState){
// this.props, this.state.qty:2 / nextState.qty:1
console.log('componentDidUpdate');
// 注意:在这里如果有setState时,一定要加条件,不然会造成死循环
// 因为生命周期中,如果这里数据更新,会render,会有一个闭环,具体看生命周期图解
}
// 3、卸载阶段
componentWillUnmount(){
console.log('componentWillUnmount');
}
/**
* 组件接收到了新的props属性。新的属性会通过 nextProps 获取到。
* 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
*/
componentWillReceiveProps(){
console.log('componentWillReceiveProps');
}
// 在props改变或state改变时被调用,
// 必须返回true或false来决定给是否重新渲染组件
shouldComponentUpdate(nextProps,nextState){
// console.log(this.state.qty) //当前值
// console.log(nextState.qty) // 将要改变的值
console.log('shouldComponentUpdate')
// return true
// 只有当qty是5的倍数时, 才继续render, 页面更新数据, 不然不执行render
// if(nextState.qty % 5 === 0){
// return true
// }else{
// return false
// }
// 只有当将要改变的值 等于传递参数的值时,再让后面的render重新执行
// 避免重新渲染,这里可以做优化部分,只有传递的参数变化时,才渲染render
// 而此时age的变化,不想让下面的组件重新render,所以加下面的条件判断;
if(nextProps.username === this.props.username){
return false
}
return true
}
render(){
console.log('render')
return (
<div>
<h1>生命周期测试</h1>
<button onClick={()=>{
this.setState({
qty:this.state.qty + 1
})
}}>点我 {this.state.qty}</button>
</div>
)
}
}
export default Lifecycle
import React from 'react'
import Lifecycle from '../components/Lifecycle'
// 有状态改变用class组件
class App extends React.Component {
state = {
show: true,
username:'fqniu',
age:18
}
render(){
return (
<div>
{
this.state.show
? <Lifecycle username={this.state.username}/>
: <div>我是div</div>
}
<button onClick={()=>{
this.setState({
show:!this.state.show
})
}}>App按钮: 点我切换显示</button>
<button onClick={()=>{
this.setState({
username:this.state.username+'plus'
})
}}>修改username:{this.state.username}</button>
<button onClick={()=>{
this.setState({
age:this.state.age+1
})
}}>修改age:{this.state.age}</button>
</div>
)
}
}
export default App
注意一点:在constructor中打印this是有参数的,虽然this里面存在有props,但是打印 this.props 则返回undefined 。如果你想要拿到这个this.props的话,则需要下面的写法:
constructor(props){
super(props)
this.state={
qty:0
}
}
console.log(this)
// 这样就可以拿到 this.props 的值了。
console.log(this.props)