🧑💻 友情链接
一、react入门
入门介绍
React:用于构建用户界面的JavaScript库。简单来说,就是一个将数据渲染为HTML视图的开源JavaScript库。
React特点:
1)采用组件化模式、声明式编码,提高开发效率及组件复用率
2)在React Native中可以使用React语法进行移动端开发
3)使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互
React高效的原因:
1)使用虚拟DOM,不是直接操作页面的真实DOM
2)DOM Diffing算法,最小化页面重绘
学习React之前需要掌握的JS基础知识:判断this的指向、class类、ES6语法规范、npm包管理器、原型/原型链、数组常用方法、模块化...等
引入方式
学习时可直接使用官方提供的CDN地址(如果想要使用脚手架,请看第三章)
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- 生产环境中不建议使用。用于将JSX转为JS的 -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
这样就可以使用两个全局变量:React 和 ReactDOM
初始案例
<body>
<!-- 准备容器 -->
<div id="test"></div>
<script type="text/babel">
// 1、创建虚拟DOM
const VDOM = <h1>Hello React</h1>
// 2、渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
虚拟DOM
第一种方式,使用JSX:
<script type="text/babel">
// 1、创建虚拟DOM
const VDOM = <h1 id="title">Hello React</h1>
// 2、渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
第二种方式,不引入babel,使用纯JS原生方式:(不推荐使用此种方式)
<script type="text/javascript">
// 1、创建虚拟DOM:<h1 id="title">Hello React</h1>
const VDOM = React.createElement('h1', {id:'title'}, 'Hello React')
// 2、渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
由此可见,JSX其实就是原生写法的语法糖,可以让我们更简单的创建虚拟DOM。虚拟DOM其本质就是Object类型的对象,他最终会被React转化为真实DOM。
console.log(typeof VDOM) // 输出结果:object
JSX语法
JSX(JavaScript XML) ,是react定义的一种类似XML的JS扩展语法(JS+XML)。其本质是 React.createElement(component, props, ...children) 方法的语法糖。其作用是用来简化创建虚拟DOM。
JSX语法规则:
- 定义虚拟DOM时,不要写引号。如
const ele = <h1>Hello React</h1> - 标签中混入JS表达式时要用 {}。如
const ele = <h1>Hello {name}</h1> - 样式的类名指定不要用“class”,要用
className(驼峰的方式变量) - 内联样式,要用
"style={{key: 'value', key: 'value'}}"的形式去写 - 只能有一个根标签
- 所有的标签必须要闭合
- 对于标签的首字母:
- 如果是小写字母开头,会将该标签转为html中的同名元素。如果html中没有该标签对应的同名元素,则会报错;
- 如果是大写字母开头,react就会去渲染对应的组件。如果组件没有定义,则会报错;
<script type="text/babel">
const h2_id = 'h2_ID'
const span_data = 'Hello React'
// 1、创建虚拟DOM
const VDOM = (
<div>
<h2 className="title" id={h2_id}>
<span style={{ color: 'red', fontSize: '20px' }}> {span_data} </span>
</h2>
<h3> {span_data+'www'} </h3>
<input type="text"/>
<Good></Good> // Good is not defined
</div>
)
// 2、渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
上面JSX规则中,如果要在标签中混入JS表达式需要用“{}” ,但是需要注意:表达式和语句不是同一个东西!
<script type="text/babel">
// 模拟数据
const data = ['React', 'Vue', 'Angular']
// 1.创建虚拟DOM
const element = (
<div>
<h1>前端JS框架</h1>
<ul>
{ // 数组增强方法
data.map((item, index)=>{
// 这里用index作为key并不合理,后续说明
return <li key={index}> {item} </li>
})
}
</ul>
</div>
)
// 2.渲染虚拟DOM到页面
ReactDOM.render(element, document.getElementById('test'))
</script>
模块&组件
- 模块:向外提供特定功能的JS程序,一般就是一个JS文件。复用JS简化编写
- 组件:用来实现局部功能效果的代码和资源的集合。复用编码提高效率
- 模块化:当应用的JS都以模块来编写的,这个应用就是一个模块化的应用
- 组件化:当应用是以多组件的方式实现,这个应用就是一个组件化的应用
可以安装开发者工具:React Developer Tools
注意在开发者工具设置里下面这俩要取消勾选:
二、react组件
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
组件定义方式
1)函数式组件(推荐)
注意: 组件名称必须以大写字母开头, 渲染时要用标签的形式
// 1.创建函数式组件
function MyComponent(props){
return (<h2>用函数定义的组件。Hello {props.name}</h2>)
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
2)类式组件
// 1.创建类式组件
class MyComponent extends React.Component {
// render会放在MyComponent的原型对象上,供实例使用
render() {
return <h2>用类定义的组件.Hello {this.props.name}</h2>
}
}
// 2.渲染组件到页面
// 执行此方法后,找到组件,随后new出来该类的实例,并调用原型对象上的render方法
ReactDOM.render(<MyComponent />, document.getElementById('test'))
组件中的render会放在MyComponent的原型对象上,供实例使用:
组件中的render()中的this指的就是MyComponent组件实例对象:
组件核心属性(state)
state 代表了随时间会产生变化的数据,应当仅在实现交互时使用
如果组件中有状态(state),那么就是复杂组件。state是组件对象最重要的属性,值是对象(多个key-value的组合)。
组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)。可以理解为:数据放在状态也就是state中,更改状态中的数据就可以引起页面的变化(状态更新时就会调用一次render()方法)。
state是私有的,并且完全受控于当前组件。
⚠️⚠️注意:
- 组件中render()方法中的this为组件实例对象;
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this:通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新,需要使用setState方法
1)state初始化
构造函数是唯一可以给 this.state 赋值的地方
// 1.创建类式组件
class Weather extends React.Component {
constructor(props) { // 参数props
super(props)
this.state = { isHot: true, wind: '大风' } //初始化状态
}
render() {
const { isHot, wind } = this.state // 读取状态
return <h2>今日天气:{isHot ? '炎热' : '凉爽'}, {wind}</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
2)setState的使用
react事件绑定需要注意:onClick的大小写和函数绑定demo不加括号
// 1.创建类式组件
class Weather extends React.Component {
constructor(props) { // 参数props
super(props)
// 初始化状态
this.state = { isHot: true, wind: '大风' }
// 解决changeWeather中的this指向问题
this.changeWeather = this.changeWeather.bind(this)
// bind返回一个新函数
}
// 此方法会放在类的原型对象上,供实例使用。
// onClick回调,非实例调用。其this为undefined
changeWeather() {
const isHot = this.state.isHot
// 【状态不可直接更改】!!!!!!
// this.state.isHot = !isHot
// 【状态必须通过setState进行修改】!!!!!!
this.setState({ isHot: !isHot })
console.log('修改isHot的值,修改后是:', this.state.isHot)
}
// 状态更新时就会调用一次render()方法
render() {
const { isHot, wind } = this.state
return <h2 onClick={this.changeWeather}>今日天气:{isHot ? '炎热' : '凉爽'}, 风向:{wind}</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
3)state简写方式(推荐)
自定义方法:【需要使用赋值语句的形式+箭头函数】
// 1.创建类式组件
class Weather extends React.Component {
state = { isHot: true, wind: '大风' }
// 自定义方法:【需要使用赋值语句的形式+箭头函数】
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
render() {
const { isHot, wind } = this.state
return <h2 onClick={this.changeWeather}>今日天气:{isHot ? '炎热' : '凉爽'}, 风向:{wind}</h2>
}
}
// 2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
export default class Clock extends Component {
constructor(props) {
super(props);
this.state = { date: new Date() }
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({ date: new Date() })
}
render() {
return (
<div>
<h1>这里是类式组件Clock</h1>
<h2>当前时间为:{this.state.date.toLocaleTimeString()}</h2>
</div>
)
}
}
4)state更新
- 不要直接修改State, 需要使用setState方法
- State的更新可能是异步的
- 要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
-
State的更新会被合并(浅合并)
组件核心属性(props)
props可以理解为传递参数使用的( props 是父组件向子组件传递数据的方式)
1)props基本使用
// 1.创建类式组件
class Person extends React.Component {
render() {
const { name, sex, age } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 2.渲染组件到页面
ReactDOM.render(<Person name="桃子" age="18" sex="男" />, document.getElementById('test'))
ReactDOM.render(<Person name="苹果" age="19" sex="女" />, document.getElementById('test2'))
2)props批量传递
const p = { name: '桃子', age: 20, sex: '男' }
ReactDOM.render(<Person {...p} />, document.getElementById('test'))
3)props限制
<!-- 引入prop-types.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/prop-types/15.8.1/prop-types.js"></script>
...
<body>
<!-- 准备容器 -->
<div id="test"></div>
<script type="text/babel">
// 1.创建类式组件
class Person extends React.Component {
render() {
const { name, sex, age } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 属性规则限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
}
// 属性默认值
Person.defaultProps = {
sex: '男',
age: 30,
}
// 2.渲染组件到页面
ReactDOM.render(<Person name="桃子" speak={speak}/>, document.getElementById('test'))
function speak(){
console.log('说话方法');
}
</script>
</body>
以上props使用是只读的,无法修改
4)props简写方式
<script type="text/babel">
// 1.创建类式组件
class Person extends React.Component {
// 属性规则限制
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
// 属性默认值
static defaultProps = {
sex: '男',
age: 20,
}
render() {
const { name, sex, age } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 2.渲染组件到页面
ReactDOM.render(<Person name="桃子" />, document.getElementById('test'))
</script>
5)props与构造器
通常在React中,构造函数仅用于以下两种情况:
- 通过给this.state赋值对象来初始化内部state(可以使用简写方式)
- 为事件处理函数绑定实例(可以使用赋值语句加箭头函数方式)
在constructor()函数中不要调用setState()方法。如果组件需要使用内部state,可以直接在构造函数中为this.state赋值初始state:
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
【开发时一般不用构造器】
6)函数式组件使用props
⚠️注意:函数式组件只能使用props,无法使用state和refs(后面可以使用Hooks)
<script type="text/babel">
// 1.创建函数式组件
function Person(props) {
const { name, sex, age } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 属性规则限制
Person.propTypes = {...}
// 属性默认值
Person.defaultProps = {...}
// 2.渲染组件到页面
ReactDOM.render(<Person name="桃子"/>, document.getElementById('test'))
</script>
组件核心属性(refs)
refs可以理解为可以越过虚拟DOM去操作真实DOM
1)字符串形式的ref
注意:这种使用形式实际上不被推荐,很少使用
<script type="text/babel">
// 1.创建类式组件
class Demo extends React.Component {
showData = () => {
console.log(this) // this指的是Demo组件的实例对象
alert(this.refs.input111.value)
}
showData2 = () => {
alert(this.refs.input222.value)
}
render() {
return (
<div>
<input ref="input111" type="text" placeholder="点击按钮提示数据" />
<button ref="button111" onClick={this.showData}>点击提示左侧输入的数据</button> <br />
<input ref="input222" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 2.渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
2)回调形式的ref
<input ref={(currentNode) => { this.input111 = currentNode }} type="text" placeholder="点击按钮提示数据" />
<input ref={currentNode => this.input222 = currentNode} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
官方文档里的说明:
如果ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的。通过将ref的回调函数定义成class的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
saveInput = (c) => {
this.input1 = c
console.log('调用:', c)
}
{/*这种内联函数的方式会导致更新过程被执行两次*/}
{/*<input ref={(currentNode) => { this.input1 = currentNode; console.log('调用:', currentNode) }} type="text" />*/}
{/*绑定class可以解决上述问题*/}
<input ref={this.saveInput} type="text" />
3)createRef(推荐)
React.createRef() 调用后会返回一个容器,该容器可以存储被ref所标识的节点。里面只能存入一个(使用比较繁琐一点。不过是官方推荐方式)
class Demo extends React.Component {
myRef = React.createRef()
myRef2 = React.createRef()
showData = () => {
console.log(this.myRef.current.value)
}
showData2 = () => {
console.log(this.myRef2.current.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧输入的数据</button> <br />
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
4)事件处理
- 通过onXxx属性指定事件处理函数(注意大小写,如原生是onclick,React中是onClick)
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件(为了兼容)
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)(为了高效)
- 通过event.target得到发生事件的DOM元素对象
- 如果需要阻止默认行为,必须显式的使用 preventDefault
如果此时发生事件的元素正好是要操作的元素,那么就可以不用ref
function showData(e) {
e.preventDefault();
console.log('You clicked showData.');
}
<button onClick={this.showData} >AAA</button>
收集表单数据
1)非受控组件
表单中输入类DOM的值,现用现取,就是非受控组件
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this
alert(`您输入的用户名:${username.value},密码:${password.value}`)
}
render() {
return (
<form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username" />
密码:<input ref={c => this.password = c} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
2)受控组件(推荐)
渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。建议使用此种方式(不使用上面ref的方式,因为官方建议不过度使用ref)
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
savePassword = (event) => {
this.setState({ password: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`您输入的用户名:${username},密码:${password}`)
}
render() {
return (
<form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username" />
密码:<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
而对于<input type="text">, <textarea> 和 <select> 之类的标签都非常相似。它们都接受一个 value 属性来实现受控组件。
<input type="text" value={this.state.value} onChange={this.handleChange} />
<textarea value={this.state.value} onChange={this.handleChange} />
<select value={this.state.value} onChange={this.handleChange}>
...
</select>
高阶函数 | 函数柯里化
高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数:
- 若A函数,接受的参数是一个函数,那么A就可以称为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称为高阶函数。
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
saveFormData = (dataType) => {
return (event) => {
// 根据dataType存入对应的数据
this.setState({ [dataType]: event.target.value })
}
}
常见的高阶函数有:
- Promise( 使用new Promise(()=>{}) )
- setTimeout( 使用setTimeout(()=>{}) )
- arr.map() ...等
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveFormData = (dataType) => {
return (event) => {
// 根据dataType存入对应的数据
this.setState({ [dataType]: event.target.value })
}
}
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`您输入的用户名:${username},密码:${password}`)
}
render() {
return (
<form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
如果不用柯里化方式:
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault() // 阻止表单提交
const { username, password } = this.state
alert(`您输入的用户名:${username},密码:${password}`)
}
render() {
return (
<form action="https://www.baidu.com" onSubmit={this.handleSubmit}>
用户名:<input onChange={(event) => { this.saveFormData('username', event) }} type="text" name="username" />
密码:<input onChange={(event) => { this.saveFormData('password', event) }} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
组件生命周期
1)引出生命周期
勾子函数(也叫生命周期回调函数)
class Life extends React.Component {
state = { opacity: 1 }
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件挂载完毕,只调用一次
componentDidMount() {
this.timer = setInterval(() => {
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
this.setState({ opacity })
}, 200);
}
// 组件将要被卸载时调用,只调用一次
componentWillUnmount(){
// 清除定时器
clearInterval(this.timer)
}
// 第一次挂载,和后续状态修改的时候会调用
render() {
return (
<div>
<h2 style={{ opacity: this.state.opacity }}>Hello World</h2>
<button onClick={this.death}>欢迎欢迎</button>
</div>
)
}
}
2)生命周期流程(旧)
React 16.8以后的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。
常用的勾子:
componentDidMount(一般做一些初始化的事情, 如开启监听, 发送请求请求 )
render(必须使用,渲染页面)
componentWillUnmount(一般做一些收尾的事情, 如:关闭定时器,取消订阅 )
新流程中即将要废弃的勾子:
componentWillMount、componentWillReceiveProps、componentWillUpdate
初始化过程: 由ReactDOM.render()触发,初次渲染
更新阶段: 由组件内部this.setSate()或父组件重新render触发
setState时:
forceUpdate时:
父组件重新render时:
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
3)生命周期流程(新)
初始化阶段: 由ReactDOM.render()触发—初次渲染
更新阶段: 由组件内部this.setSate()或父组件重新render触发
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
4)getSnapshotBeforeUpdate
<body>
<div id="test"></div>
<script type="text/babel">
class NewsList extends React.Component {
state = { newsArr: [] }
// 更新新闻内容
componentDidMount() {
setInterval(() => {
const { newsArr } = this.state
const news = '新闻新闻' + (newsArr.length + 1)
this.setState({ newsArr: [news, ...newsArr] })
}, 1000);
}
getSnapshotBeforeUpdate() {
// 内容区高度
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps, preState, height) {
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render() {
return (
<div className="list" ref="list">
{this.state.newsArr.map((news, index) => {
return <div key={index} className='news'>{news}</div>
})}
</div>
)
}
}
ReactDOM.render(<NewsList />, document.getElementById('test'))
</script>
</body>
虚拟DOM & DOM Diff算法
react中的key就是虚拟DOM对象的标识,在更新显示时起到很重要的作用。当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,随后react进行新虚拟DOM与旧虚拟DOM的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key:
- 根据数据创建新的真实DOM,然后渲染到页面
如果使用index作为key可能会引发的问题:
- 若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生没必要的真实DOM更新(页面效果没问题,但效率低)
- 如果不存在对数据进行逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,那么使用index作为key是没问题的
- 如果结构中还包含输入类的DOM,会产生错误DOM更新(页面效果有问题)
选择使用key的注意事项:
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等信息
- 如果确定只是简单的数据展示,也可以使用index
- 在 map() 方法中的元素需要设置key属性
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: '张三', age: 18 },
{ id: 2, name: '李四', age: 19 },
]
}
add = () => {
const { persons } = this.state
const wang = { id: persons.length + 1, name: '王五', age: 20 }
this.setState({ persons: [wang, ...persons] })
}
render() {
return (
<div>
<h2>人员列表</h2>
<button onClick={this.add}>添加王五信息</button>
<ul>
{this.state.persons.map((person, index) => {
return <li key={index}>{person.id}={person.name}={person.age} <input type="text"/></li>
})}
</ul>
<hr />
<ul>
{this.state.persons.map((person) => {
return <li key={person.id}>{person.id}={person.name}={person.age} <input type="text"/></li>
})}
</ul>
</div>
)
}
}
条件渲染
- 元素变量配合if进行展示
if (isLoggedIn) {
button = <LogoutButton />;
} else {
button = <LoginButton />;
}
- 通过与运算符 &&
- true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
{unreadMessages.length > 0 &&
<h2> You have {unreadMessages.length} unread messages. </h2>
}
- 三目运算符
{isLoggedIn? <LogoutButton /> : <LoginButton />}
三、react应用
react脚手架
xxx脚手架:是用来帮助程序员快速创建一个基于xxx库的模板项目。其中:包含了所有需要的配置(语法检查、JSX编译、devServer等)、下载好了所有相关的依赖、可以直接运行一个简单效果。
react提供了一个用于创建react项目的脚手架库:creat-react-app。项目的整体技术架构为:react+webpack+es6+eslint
使用脚手架开发项目的特点就是:模块化、组件化、工程化
创建项目并启动:
- 全局安装:
npm install -g create-react-app - 切换到想创建项目的目录,使用命令:
create-react-app hello-react - 进入项目文件夹:
cd hello-react - 启动项目:
npm start
脚手架文件
-
public文件夹中一般存放静态资源文件
- index.html中定义了容器:<div id="root">
-
src文件放的就是核心代码文件
-
App.js+App.css:函数式组件并暴露出去了, function App() { return() } 、 export default App;
-
index.js+index.css:入口文件
-
自定义组件流程
index.js作为入口文件:
ReactDOM.render(<App />, document.getElementById('root'))
App.js作为主页面,里面引入需要展示的组件:
export default class App extends Component {
render() {
return (
<div>
<Hello />
<Welcome />
</div>
)
}
}
对应组件则可显示自己的内容:
export default class Hello extends Component {
render() {
return <p className='title'>Hello React, Im Hello ! </p>
}
}
组件开发知识点
- 拆分组件,实现静态组件,需要注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中呢?
- 某个组件自己使用:就放在自身的state中
- 某些组件都会使用:放在他们共同的父组件state中(状态提升)
- 关于父子组件之间的通信:
- 【父组件】给【子组件】传递数据:通过props
- 【子组件】给【父组件】传递数据:通过props,要求父组件提前给子组件传递一个函数
- 注意 defaultChecked 和checked 的区别,类似的还有 defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
详细说明可以查看官方这片文章【React哲学】:zh-hans.reactjs.org/docs/thinki…
四、react Ajax
前置说明
React本身只关注界面,并不包含发送ajax请求的代码。前端需要通过ajax请求与后台进行交互。所以React应用中需要集成第三方ajax库。常见的ajax请求库有:
1、jQuery:比较重,不建议使用
2、axios:轻量级,基于promise可以用于浏览器和node.js的网络请求库
axios官方文档学习:axios-http.com/zh/docs/int…
每日一句案例
import React, { Component } from 'react';
import axios from 'axios';
export default class App extends Component {
state = { info: '' }
getStudentData = () => {
axios.get('https://v.api.aa1.cn/api/yiyan/index.php').then(
response => { this.setState({ info: response.data }) },
error => { console.log('请求失败,错误信息为:', error) }
)
}
render() {
return (
<div>
<button onClick={this.getStudentData}>每日一句</button>
<div dangerouslySetInnerHTML={{ __html: this.state.info }} />
</div>
)
}
}
github搜索案例
import React, { Component } from "react";
import Search from "./components/Search";
import List from "./components/List";
export default class App extends Component {
state = {
users: [],
isFirst: true, // 是否首次打开页面
isLoading: false, // 是否搜索加载中
err: '', // 存储请求错误信息
};
updateAppState = (stateObj) => {
this.setState( stateObj );
};
render() {
return (
<div>
<Search updateAppState={this.updateAppState} />
<List {...this.state}/>
</div>
);
}
}
import React, { Component } from "react";
import axios from 'axios';
export default class Search extends Component {
handleSearch = () => {
// 获取用户输入并重命名
const { keyWordElement: { value: keyWord } } = this
// 发送请求前更新状态:
this.props.updateAppState({ isFirst: false, isLoading:true });
// 发送网络请求
axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
(response) => {
this.props.updateAppState({ isLoading: false, users: response.data.items });
},
(error) => {
this.props.updateAppState({ isLoading: false, err: error.message });
}
);
}
render() {
return (
<section className="jumpbotron">
<h3 className="jumpbotron-heading">搜索Github用户</h3>
<div>
<input ref={c => this.keyWordElement = c} type="text" placeholder="输入昵称关键词" />
<button onClick={this.handleSearch}>搜索</button>
</div>
</section>
);
}
}
/* eslint-disable jsx-a11y/alt-text */
import React, { Component } from 'react'
import './index.css'
export default class List extends Component {
render() {
const { users, isFirst, isLoading, err } = this.props;
return (
<div className="row">
{
isFirst ? <h2>初始化状态</h2> :
isLoading ? <h2>Loading...</h2> :
err ? <h2>{err}</h2> :
users.map((userObj) => {
return (
<div className="card" key={userObj.id}>
<a href={userObj.html_url} target="_blank" rel="noreferrer">
<img src={userObj.avatar_url} style={{ width: "100px" }} />
<p className='card-text'>{userObj.login}</p>
</a>
</div>)
})
}
</div>
)
}
}
消息订阅发布机制
工具库:PubSubJS
项目git地址:github.com/WanderLV/Pu…
下载: npm install pubsub-js
import React, { Component } from "react";
import Search from "./components/Search";
import List from "./components/List";
export default class App extends Component {
render() {
return (
<div>
<Search />
<List />
</div>
);
}
}
import React, { Component } from "react";
import PubSub from "pubsub-js";
import axios from "axios";
export default class Search extends Component {
handleSearch = () => {
// 获取用户输入并重命名
const { keyWordElement: { value: keyWord } } = this;
// 发送请求前通知List更新状态,发布消息
PubSub.publish("github user", { isFirst: false, isLoading: true });
// 发送网络请求
axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
(response) => {
PubSub.publish("github user", {
isLoading: false,
users: response.data.items,
});
},
(error) => {
PubSub.publish("github user", {
isLoading: false,
err: error.message,
});
}
);
};
render() {
return (
<section className="jumpbotron">
<h3 className="jumpbotron-heading">搜索Github用户</h3>
<div>
<input
ref={(c) => (this.keyWordElement = c)}
type="text"
placeholder="输入昵称关键词"
/>
<button onClick={this.handleSearch}>搜索</button>
</div>
</section>
);
}
}
/* eslint-disable jsx-a11y/alt-text */
import React, { Component } from "react";
import PubSub from "pubsub-js";
import "./index.css";
export default class List extends Component {
state = {
users: [],
isFirst: true, // 是否首次打开页面
isLoading: false, // 是否搜索加载中
err: "", // 存储请求错误信息
};
componentDidMount() {
// 初始化操作-订阅消息(谁需要数据谁就订阅)
this.token = PubSub.subscribe("github user", (_, stateObj) => {
this.setState(stateObj);
});
}
componentWillUnmount() {
// 取消订阅消息
PubSub.unsubscribe(this.token);
}
render() {
const { users, isFirst, isLoading, err } = this.state;
return (
<div className="row">
{isFirst ? (
<h2>初始化状态</h2>
) : isLoading ? (
<h2>Loading...</h2>
) : err ? (
<h2>{err}</h2>
) : (
users.map((userObj) => {
return (
<div className="card" key={userObj.id}>
<a href={userObj.html_url} target="_blank" rel="noreferrer">
<img src={userObj.avatar_url} style={{ width: "100px" }} />
<p className="card-text">{userObj.login}</p>
</a>
</div>
);
})
)}
</div>
);
}
}
fetch发送请求
项目git地址:github.com/github/fetc…
fetch是原生函数,无需下载即可使用
import React, { Component } from "react";
import PubSub from "pubsub-js";
import axios from "axios";
export default class Search extends Component {
handleSearch = async () => {
// 获取用户输入并重命名
const {
keyWordElement: { value: keyWord },
} = this;
// 发送请求前通知List更新状态,发布消息
PubSub.publish("github user", { isFirst: false, isLoading: true });
// 发送网络请求
try {
const response = await fetch(
`https://api.github.com/search/users?q=${keyWord}`
);
const data = await response.json();
console.log("获取数据成功:", data);
PubSub.publish("github user", {
isLoading: false,
users: data.items,
});
} catch (error) {
console.log("获取数据失败", error);
PubSub.publish("github user", {
isLoading: false,
err: error.message,
});
}
};
render() {
return (
<section className="jumpbotron">
<h3 className="jumpbotron-heading">搜索Github用户</h3>
<div>
<input
ref={(c) => (this.keyWordElement = c)}
type="text"
placeholder="输入昵称关键词"
/>
<button onClick={this.handleSearch}>搜索</button>
</div>
</section>
);
}
}
网络请求知识点
1、ES6知识点:解构赋值+重命名
- let obj = {a:{b:1}}
- const {1} = obj; //传统解构赋值
- const {a:{b}} = obj; //连续解构赋值
- const {a:{b:value}} = obj; // 连续解构赋值+重命名
2、消息订阅与发布机制
- 先订阅,再发布
- 适用于任意组件之间的通信
- 要在组件的 componentWillUnmount() 中取消订阅
3、fetch发送请求,要关注分离的设计思想
// 发送网络请求
try {
const response = await fetch(
`https://api.github.com/search/users?q=${keyWord}`
);
const data = await response.json();
console.log("获取数据成功:", data);
} catch (error) {
console.log("获取数据失败", error);
}
剩余内容请看下文:juejin.cn/post/721253…