跟着下面的步骤来!!
shift+鼠标右键 windows PowerShell^ ^
create-react-app my-app(文件名)
cd my-app
npm start
好了,你已经创建了一个react项目文件了捏❥(ゝω・✿ฺ)
1. 工程目录简介
scr目录下
index.js - 入口文件
index.js里可以引入 css 文件,之前的时候说过css文件只能在 html 文件里引入
这个是可以通过webpack配置,就可以在 js 里引入 css 文件了
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
这里 发现下面没有用到 React 但是不能把 import React from 'react'删掉
因为 ReacDOM.render里 <App/>用到的是 JSX 语法,要引入 'react'来解析这种语法
还有就是 React.StrincMode相当于开启严格模式,有助于:
- 识别具有不安全生命周期的组件
- 有关旧式字符串ref用法的警告
- 检测意外的副作用
- 检测遗留 context API
具体参考官方文档(react.caibaojian.com.cn/docs/strict…)
App.js帮助生成文本
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
ReactDOM.render把app挂载到页面上
App.test.js 是自动化测试的文件
2. React 基础入门
1. react 中的组件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Test from './Test'
ReactDOM.render(
<div>
<App />
<Test />
</div>
,
document.getElementById('root')
);
就这样挂载上去
2. 什么是JSX语法
JSX是对js语法的拓展
JSX 语法里面,有两类型的标签
JSX 第一种标签:普通的 html 标签 ex: div、p
JSX 第二种标签:组件标签,上面 <App />注意:组件的标签是大写的
3. 使用 React 编写 TodoList 功能
import React,{ Fragment } from 'react'
function TodoList() {
return (
<Fragment>
<input />
<ul>
<li>
Learn React
</li>
<li>
Learn React2
</li>
</ul>
</Fragment>
)
}
export default TodoList;
Fragment占位符
4. React 中数据驱动的设计思想和事件绑定
import React,{ Component, Fragment } from 'react'
class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: 'hello world',
list: []
}
}
render() {
return (
<Fragment>
<input value={this.state.inputValue} />
<ul>
<li>Learn React</li>
<li>Learn React2</li>
</ul>
</Fragment>
)
}
}
export default TodoList;
理解:
constructor 构造函数,根据之前学的 class 自己理解
react里 数据是要写在 this.state里面的
接着上面的 TodoList 当我们在 input 框里输入内容的时候,input 框里的内容要跟着变化,先看上面 input 里绑定的数据是 this.state.inputValue所以不管在输入框里输入什么,显示的始终是this.state.inputValue的值
所以要在 input 框上面绑定 onChange 事件
handleInputChange(e) {
console.log(e.target.value)
}
render() {
return (
<Fragment>
<input
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<ul>
<li>Learn React</li>
<li>Learn React2</li>
</ul>
</Fragment>
)
}
}
在上面的 handleInputChange 里 打印出来的结果是输入框里面的内容
再理解一下 onChange={this.handleInputChange.bind(this)}
如果不用 bind 传入 this 那么,在handleInputChange里打印出的 this 是 undefined
这是为什么呢?
react 类组件中绑定事件处理函数时,相当于 js 中将函数直接作为事件处理函数一样,这时事件应该指向事件源,由于 react-cli 自身的严格模式导致 this 指向 undefined
然后在 react 里面规定 修改 state 里的属性时要调用 setState
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
这样 input 框里的内容会根据输入的内容改变了
5. 实现 TodoList 新增删除功能
首先是ul里的li标签内容应该是循环展示 list 里的值
<ul>
{
this.state.list.map((value, index) => {
return <li key={index}>{value}</li>
})
}
</ul>
这个 key 后面应该会说到时候来补充^ ^(暂时理解为跟 vue 里的key相同)
然后是当我们按下 enter 的时候,我们输入的内容展示在下面的li标签里
<input
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
onKeyUp={this.handleKeyUp.bind(this)}
/>
先在 input 框里面绑定 onKeyUp 事件
handleKeyUp(e) {
console.log(e.keyCode)
}
这样可以在控制台里获得 enter 键的键码 -> 13
handleKeyUp(e) {
if(e.keyCode === 13) {
const list = [...this.state.list, this.state.inputValue]
this.setState({
list: list
})
}
}
先把原先 list 的值拷贝一份,然后把新的值加进去,然后再修改list
可以简化一下上面的代码 list: list可以写成list
另一个优化就是,当我们 enter 之后,输入框里的内容应该清空
handleKeyUp(e) {
if(e.keyCode === 13) {
const list = [...this.state.list, this.state.inputValue]
this.setState({
list,
inputValue: ''
})
}
}
这样添加的功能就ok惹^ ^
删除功能:点击li标签,对应的内容就被删除掉
先在li标签上绑定 onclick 事件(注意看格式)
<ul>
{
this.state.list.map((value, index) => {
return (
<li key={index} onClick={this.handleItemClick.bind(this, index)}>
{value}
</li>
)
})
}
</ul>
return后面的内容加个括号这样就可以换行,bind 方法里可以传参
handleItemClick(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({list})
}
还是先把this.state.list里的值拷贝一份,然后用splice方法删掉指定的一项
6. 更多JSX语法细节
对上面的 TodoList 进行优化
首先回顾一下 bind 函数,bind 函数传入的参数分成两部分,第一个参数是函数上下文作用的对象,后面则是参数列表。bind 函数是返回一个改变了 this 指向的函数,而原函数的 this 没有被改变
每执行一次点击或者change事件,都要执行一次bind函数,是非常消耗性能的,我们可以把这一步写在constructor里面
constructor(props) {
super(props)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.state = {
inputValue: 'hello world',
list: []
}
}
handleItemClick不能写在ocnstructor里面,因为这个方法要传入参数,每次传递的参数是不同的
然后就是在render()函数里,对li标签的循环我们可以单独写在一个函数里
getItemList() {
return this.state.list.map((value, index) => {
return (
<li key={index} onClick={this.handleItemClick.bind(this, index)}>
{value}
</li>
)
})
}
render() {
return (
<Fragment>
<input
value={this.state.inputValue}
onChange={this.handleInputChange}
onKeyUp={this.handleKeyUp}
/>
<ul>
{this.getItemList()}
</ul>
</Fragment>
)
}
这样基本上就优化完成了,然后贴一下完整的代码
TodoList.js
import React,{ Component, Fragment } from 'react'
class TodoList extends Component {
constructor(props) {
super(props)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.state = {
inputValue: 'hello world',
list: []
}
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value
})
}
handleKeyUp(e) {
if(e.keyCode === 13) {
const list = [...this.state.list, this.state.inputValue]
this.setState({
list,
inputValue: ''
})
}
}
handleItemClick(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({list})
}
getItemList() {
return this.state.list.map((value, index) => {
return (
<li key={index} onClick={this.handleItemClick.bind(this, index)}>
{value}
</li>
)
})
}
render() {
return (
<Fragment>
<input
value={this.state.inputValue}
onChange={this.handleInputChange}
onKeyUp={this.handleKeyUp}
/>
<ul>
{this.getItemList()}
</ul>
</Fragment>
)
}
}
export default TodoList;
-
假如我们在 TodoList 里面引入一个 css 文件
style.css
.input { border: 1px solid green; }如果我们在 input 标签上给它添加
class='input'控制台会报错,因为我们这个组件是 class 组件,两个都叫 class 可能会混淆,所以在 input 标签上给它添加类名的时候应该写成className='input' -
在输入框前面加一个
请输入内容:然后点击这段文字,输入框就就会聚焦<label for='myinput'>请输入内容:</label> <input id='myinput' className='input' value={this.state.inputValue} onChange={this.handleInputChange} onKeyUp={this.handleKeyUp} />如果用
for控制台就会报错,因为在js语法里面for是一个循环语句,所以在这里要用htmlFor -
在jsx语法里面写注释
{/* 这是一段注释 */} -
在上面的TodoList里,在 input 框里面直接按回车,下面
li标签也会添加空的内容,可以添加一个判断就可以阻止handleKeyUp(e) { if(e.keyCode === 13 && e.target.value !== '') { const list = [...this.state.list, this.state.inputValue] this.setState({ list, inputValue: '' }) } } -
假如我们在输入框里面输入
<h1>hello</h1>下面的li标签会对h1标签进行转义,但是我想做的就是显示h1标签,那么:<li key={index} onClick={this.handleItemClick.bind(this, index)} dangerouslySetInnerHTML={{__html: value}} > </li>
3. React组件与生命周期
1. 组件拆分与组件之间的传值
接着上面的TodoList,我们可以把下面的li标签拆分成一个一个的组件
TodoItem.js
import React,{Component} from "react";
class TodoItem extends Component {
render() {
return <li>{this.props.content}</li>
}
}
export default TodoItem
TodoList.js
getItemList() {
return this.state.list.map((value, index) => {
return <TodoItem content={value} />
})
}
对比之前的代码,这里返回的是TodoItem组件 父组件通过属性的形式向子组件传值,子组件用props接收父组件传来的值
上面子组件接收父组件传来的值还可以用es6的解构赋值来写
const {content} = this.props
return <li>{content}</li>
然后是做删除的功能
不能直接在子组件上面绑定click事件,要在子组件里面去绑定
TodoItem.js
import React,{Component} from "react";
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleItemClick = this.handleItemClick.bind(this)
}
handleItemClick() {
const { deleteFunction, index } = this.props
deleteFunction(index)
}
render() {
return <li onClick={this.handleItemClick}>{this.props.content}</li>
}
}
export default TodoItem
在li标签上绑定onClick事件
handleItemClick(index) {
const list = [...this.state.list]
list.splice(index, 1)
this.setState({list})
}
getItemList() {
return this.state.list.map((value, index) => {
return (
<TodoItem
key={index}
index={index}
content={value}
deleteFunction={this.handleItemClick}
/>
)
})
}
父组件向子组件传递handleItemClick方法(deleteFunction是属性)
子组件接收到这个方法并调用它,传入当前组件的index值,即调用父组件的handleItemClick方法
父组件通过属性的形式向子组件传值
子组件想要和父组件通信,它要调用父组件传递过来的方法
3. React的核心特性总结
-
声明式开发(原生js、jquery是命令式,页面上展示什么需要不断的操作dom)
也就是我们不需要操作dom,只需要定义好jsx模板和数据,数据发生变化,页面的dom自动就会跟着发生变化
-
可以与其他框架并存
我们把组件挂载到哪个节点上,react只会处理那一个节点,其他的节点是不会处理的,所以其他的节点我们也可以用jquery、vue来处理也是可以的^ ^
-
组件化
页面被拆分成一个一个的组件,通过组件之间的通信来实现整个页面的功能
-
单向数据流(vue也是单向数据流^ ^)
父组件可以改变子组件的数据,子组件一定不能直接改变父组件的数据
上面的例子
<TodoItem key={index} index={index} content={value} deleteFunction={this.handleItemClick} />当index、value发生变化的时候,子组件接收到的值也就变化了
但是当子组件想改变父组件的数据时,我们是调用父组件的方法改变父组件的数据(其实也可以说是父组件自己改变自己的数据)
-
函数式编程
4. Props, State 与 render 函数
props指的是属性,父组件通过属性的形式向子组件传递参数
state指的是组件里的数据
render函数用来渲染组件里的内容
render()函数什么时候会被执行
- 当组件初次创建的时候,render函数会被执行一次
- 当state数据发生变更的时候,render函数会被重新执行
- 当props数据发生变更的时候,render函数会被重新执行
5. React 中 ref 的使用
-
ref 写在 html 标签上
<button onClick={this.handleButtonClick} ref={(button) => {this.buttonElem = button}} > 增加 </button>获取的是dom节点
console.log(this.buttonElem)打印出来的是
<button>增加</button> -
ref写在组件标签上
获取的是组件的js实例
-
应用
handleButtonClick() { const newCounter = this.state.counter + 1 console.log(this.divElem.innerHTML) this.setState({ counter: newCounter }) console.log(this.divElem.innerHTML) } render() { return ( <Fragment> <button onClick={this.handleButtonClick} > 增加 </button> <div ref={(div) => {this.divElem = div}} >{this.state.counter}</div> </Fragment> ) }上面的第二个
console.log(this.divElem.innerHTML)理想状态下是按一下按钮 输出
1 2但实际上这样写输出的都是1因为
this.state是异步执行的,也就是后面的console.log先执行,联系到eventloopthis.setState(() => { return { counter: newCounter } }, () => { console.log(this.state.counter) })这样写就打印出来的是
1 2理解一下:
setState可以接受两种参数,
第一种:一个对象
第二种:函数(上面的第二个代码),第一个参数可以修改值,第二个参数是回调函数
6. React 中的生命周期函数
在React中,生命周期函数指的是组件在某一时刻会自动执行的函数
constructor也是生命周期函数,但是它不是React生命周期函数
当数据发生变化时,render函数会被自动执行,render函数是一个react生命周期函数
Initialization,组件初始化。执行constructor(),注意它不是React生命周期函数
Mounting,挂载阶段。render()函数自动执行,页面第一次渲染的时候,才会执行Mounting整个过程
简单理解一下:
componentWillMount:挂载前
render():挂载
componentDidMount:挂载完成
页面更新的时候,执行的是 Updation,更新阶段。分为两种,一种是props发生变化,另一种是states发生变化
先不管 componentWillRececiveProps,这里还有四个生命周期函数
shouldComponentUpdate() componentWillUpdate() render() componentDidUpdate()
假如,我们触发了一个点击事件,但是数据没有发生变化,但是更新阶段还是会执行这四个函数,会比较消耗性能,我们可以在shouldComponentUpdate()里:
shouldComponentUpdate() {
return false;
}
这样后面的三个阶段都不会执行了,就比较提升性能^ ^
Unmounting阶段
当组件被移除的时候会执行componentWillUnmount()
componentWillReceiveProps
上面有一个功能,点击按钮,下面的数字加一,数字写在子组件里
渲染页面时,生命周期函数的执行顺序
f constructor
f componentWillMount
f render
c constructor
c componentWillMount
c render
c componentDidMount
f componentDidMount
当点击按钮,触发页面更新的时候
f componentDidMount
f shouldComponentUpdate
f componentWillUpdate
f render
c componentWillReceiveProps
c shouldComponentUpdate
c componentWillUpdate
c render
c componentDidUpdate
f componentDidUpdate
这个componentWillReceiveProps()只会在子组件里面触发,而父组件不会,因为父组件里根本就没有props!!
7. 生命周期函数使用实例
在全局绑定的函数,一定要在componentWillUnmount()销毁掉
使用 axios 发送数据,放在componentDidMount()
8. React 中的前端路由
BrowserRouter
Route
Link
<BrowserRouter>
<div>
<Route path='/list' component={NewList} />
<Route path='/button' component={NewButton} />
</div>
</BrowserRouter>
在路径里后面加 /list就能跳转到 NewList 组件页面
加/button就能跳转到 NewButton 组件页面
假如我们想点击按钮跳转页面
<Link to='/list'>
<Button type='primary'>按钮</Button>
</link>
不能在 Button 外面包裹a标签
当我们跳转页面的时候想传递一个值
<Link to='/list?a=123'>
<Button type='primary'>按钮</Button>
</link>
用这种方式的话我们不能直接获得 a=123 只能得到 ?a=123 需要我们自己解析,还有一种方法是通过路径的方式传递参数,在配置路由的时候像下面这样写
<Route path='/list/:id' component={NewList} />
这样 123 匹配的就是 :id
<Link to='/list/123'>
4. React 实现新闻网站
1. 在使用 ant design 时遇到的一些问题
在做顶部导航栏的时候,用axios获取导航栏的数据的时候,页面会有一个闪烁的情况,就是一开始导航栏是展开的,但是很快导航栏就自动折叠了。
我自己的理解是:页面进行第一次渲染的时候,list 是空数组,所以 menu 有一个默认的宽度,但是用 axios 请求到了数据之后,理想状态下的导航栏宽度是大于了初始化时的宽度,然后导航栏就被折叠了。
解决办法是给 menu 设置一个初始宽度o.o
2. 做网站时遇到的一些问题
-
配置路由的时候,如果像下面这样配置
<Content className="content"> <BrowserRouter> <Fragment> <Route path='/' component={List} /> <Route path='/detail' component={Detail} /> </Fragment> </BrowserRouter> </Content>当我们切换路径的时候,
/只展示 List,但是/detail,List Detail 两个组件都会展示解决方法,要用到路由里的新api
Switch<Content className="content"> <BrowserRouter> <Switch> <Route path='/' component={List} /> <Route path='/detail' component={Detail} /> </Switch> </BrowserRouter> </Content>这样子写,输入路径
/展示 List但是输入
/detail,仍然展示 List!这个
Switch会对输入的路径进行匹配,当我们输/detail时,/ 已经和 List 匹配上了,所以就会展示 List,要解决这个问题只需要把 List 和 Detail的位置调换一下<Route path='/detail' component={Detail} /> <Route path='/' component={List} />这样子就ok了
-
还有就是在导航栏对每个菜单配置路由的时候,先看下面的代码:
<Menu.Item key={item.id}> <Link to='/ccc'> {item.title} </Link> </Menu.Item>emm 这样写的话,页面会报错
然后回到父组件
<Layout style={{ minWidth: 1300, height: '100%' }}> <Header className="header"> <AppHeader /> </Header> <Content className="content"> <BrowserRouter> <Switch> <Route path='/detail' component={Detail} /> <Route path='/' component={List} /> </Switch> </BrowserRouter> </Content> <Footer className="footer">@copyright-Jennie 2021</Footer> </Layout>这个 List 列表是在
AppHeader这个组件里面的,但是这个组件并没有包裹在BrowserRouter里面,所以Link标签是不能使用的,解决办法<BrowserRouter> <Layout style={{ minWidth: 1300, height: '100%' }}> <Header className="header"> <AppHeader /> </Header> <Content className="content"> <Switch> <Route path='/detail' component={Detail} /> <Route path='/' component={List} /> </Switch> </Content> <Footer className="footer">@copyright-Jennie 2021</Footer> </Layout> </BrowserRouter>用
BrowserRouter把整个Layout包裹起来 -
导航栏跳转路由,整理一下过程:
先是在导航栏选择不同的选项
Header index.js
getMenuItems() { return this.state.list.map(item => { return ( <Menu.Item key={item.id}> <Link to={`/${item.id}`}> {item.title} </Link> </Menu.Item> ) }) }把每个选项用
Link标签包裹住,在路由中传入 id 的值,就能跳转到不同的页面注意 要在根路径后面加
:id<Route path='/:id' component={List} />然后在 List index.js 里:
首先是,要根据路由传参,传入的参数不同,展示不同页面的数据
上面写的有
this.props.match.params.id,可以获得传入的参数。要考虑到的一个问题是,我们通过/根路径请求页面时,匹配不到/:id,这时页面就没有数据,但是一般情况下应该是默认展示id = 1的页面,所以要<Route path='/:id?' component={List} />这里的?就是这个 id 可传可不传,但是不传的话 默认为 undefined,所以我们应该在请求数据时做一个判断componentDidMount() { let url = 'http://www.dell-lee.com/react/api/list.json' const id = this.props.match.params.id; if (id) { url = url + '?id=' + id } axios.get(url) .then((res) => { this.setState({ data: res.data.data }) }) }如果 id 有,那就拼上,没有的话就为原来的 url ,这个数据的处理后端应该会做,也就是没有传 id 的时候默认显示 id = 1的数据
但是这样子写,当我们切换导航栏的时候,只有刷新页面,页面的数据才会更新。
原因是:
componentDidMount()在页面挂载完后执行,页面没刷新的情况下,只会挂载一次,所以当点击不同的菜单项时,页面不会重新渲染,所以这个钩子函数只会执行一次。解决方法:
componentWillReceiveProps(nextProps) { const id = nextProps.match.params.id axios.get('http://www.dell-lee.com/react/api/list.json?id=' + id) .then((res) => { this.setState({ data: res.data.data }) }) }当我们点击不同的菜单选项,就会传入不同的参数,那么
componentWillReceiveProps()这个钩子函数就会执行,所以我们在这里面重新发送请求就ok了 -
emm 点击 logo 返回默认页面
自己写,很简单,忽略^ ^
这个新闻网站要结合自己写的代码来看(◕ᴗ◕✿)