组件化开发必然会需要组件间传值,我们通过一个案例来学习组件传值,需求如下:
分析和实现
首先,我们可以将需求部分分为三个组件:父组件、新增组件(输入框部分)、和列表组件
// App - 父组件
class App extends React.Component {
render() {
return (
<div>
<h2>数组遍历</h2>
<Add />
<List />
</div>
)
}
}
// Add - 新增组件
class Add extends React.Component {
render() {
return (
<div>
<input type="text" />
<button>add</button>
</div>
)
}
}
// List - 列表组件
class List extends React.Component {
render() {
return (
<ul>
<li></li>
<li></li>
</ul>
)
}
}
到目前为止,我们已经将静态页面搭建好了,接下来就是渲染数据,这时我们遇到一个问题,就是将数据放在哪个组件的 state
中 ?
如果数据是单个组件使用,那就放在这个组件的 state
中,如果数据是多个组件共用,那就放在使用数据的几个组件的父组件中。
所以这里的数据应该放在 App
中。
数据存放好之后,我们开始渲染,由于渲染是在 List
中渲染,所以我们需要想办法将数组传入 List
中,这里我们遇到了第一种传值方式 ---- 父向子传值,我们用 attribute
将数据传进去,并在子组件中用 props
属性接收:
// App - 父组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: ['吃饭', '睡觉', '喝水']
}
}
render() {
return (
<div>
<h2>数组遍历</h2>
{/* 传入列表 */}
<Add todos={this.state.todos} />
<List todos={this.state.todos} />
</div>
)
}
}
// Add - 新增组件
class Add extends React.Component {
render() {
const { todos } = this.props
return (
<div>
<input type="text" />
<button>add #{todos.length}</button>
</div>
)
}
}
// List - 列表组件
class List extends React.Component {
render() {
// 用 props 接收数据并解析
const { todos } = this.props
return (
<ul>
{/* 渲染列表 */}
{ todos.map((v, i) => <li key={i}>{v}</li>) }
</ul>
)
}
}
页面渲染完成后,我们需要做的最后一步就是交互:在输入框中输入数据,点击按钮,需要将输入的数据塞进数组,并且清空输入框的内容,这里我们遇到了第二种传值方式 ---- 父向子传值。
注意:我们不能直接将数组传入 Add
组件并且修改,这在 React 中是不允许的,因为 React 中的数据流是向下的,所以我们不能在组件中修改 props
里的属性,这里我们需要通过一个载体将 Add
组件中的值传入到 App
组件,并在 App
组件中对数组进行修改。
这个载体是一个函数,而且这个函数是父组件通过父向子传值的方式提供给子组件的:
// App - 父组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: ['吃饭', '睡觉', '喝水']
}
// 2. 绑定 addTodo
this.addTodo = this.addTodo.bind(this)
}
// 1. 声明操作函数
addTodo(todo) {
const { todos } = this.state
todos.unshift(todo)
this.setState({ todos })
}
render() {
return (
<div>
<h2>数组遍历</h2>
{/* 3. 传入 addTodo */}
<Add todos={this.state.todos} addTodo={this.addTodo} />
<List todos={this.state.todos} />
</div>
)
}
}
// Add - 新增组件
class Add extends React.Component {
constructor(props) {
super(props)
// 5. 绑定 add
this.add = this.add.bind(this)
}
// 4. 声明点击事件
add() {
// 8. 根据 refs 取值并传递
const val = this.myInput.value
this.props.addTodo(val)
this.myInput.value = ''
}
render() {
const { todos } = this.props
return (
<div>
{/* 7. 标记 refs 方便取值 */}
<input type="text" ref={input => this.myInput = input} />
{/* 6. 绑定点击事件 */}
<button onClick={this.add}>add #{todos.length}</button>
</div>
)
}
}
总结
组件传值可以总结出以下几点:
- 公共数据要放到共同的父组件中
- 父向子传值利用了组件的
props
属性 - 子向父传值是
props
属性和函数相结合
基本上组件传值的逻辑类似于 Vue ,最后附上完整代码:
- index.html
<body>
<div id="app"></div>
</body>
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../node_modules/babel-standalone/babel.min.js"></script>
<script src="./index.js" type="text/babel"></script>
- index.js
// App - 父组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: ['吃饭', '睡觉', '喝水']
}
this.addTodo = this.addTodo.bind(this)
}
addTodo(todo) {
const { todos } = this.state
todos.unshift(todo)
this.setState({ todos })
}
render() {
return (
<div>
<h2>数组遍历</h2>
<Add todos={this.state.todos} addTodo={this.addTodo} />
<List todos={this.state.todos} />
</div>
)
}
}
// Add - 新增组件
class Add extends React.Component {
constructor(props) {
super(props)
this.add = this.add.bind(this)
}
add() {
const val = this.myInput.value
this.props.addTodo(val)
this.myInput.value = ''
}
render() {
const { todos } = this.props
return (
<div>
<input type="text" ref={input => this.myInput = input} />
<button onClick={this.add}>add #{todos.length}</button>
</div>
)
}
}
// List - 列表组件
class List extends React.Component {
render() {
const { todos } = this.props
return (
<ul>
{todos.map((v, i) => <li key={i}>{v}</li>)}
</ul>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
补充(传值的第二种方式)
利用 props 传值有几个弊端:
- 如果是父 - 孙之间的组件传值,需要先将值传递给子组件
- 如果是兄弟之间的组件传值,需要先将值传递给共同的父组件
这么做代码实现起来很复杂,有没有什么方法可以直接令两个组件的值进行传递呢?可以通过第三方工具 PubSub-js
PubSub-js 组件传值利用的原理是发布和订阅,有需要传出数据的组件进行发布数据,在需要接入数据的组件中进行订阅,就实现了组件传值。
引入
首先,我们需要安装 PubSub-js :
npm install pubsub-js
然后,在发布数据和订阅数据的组件中,都需要引入 PubSub-js :
import PubSub from 'pubsub-js'
发布(publish)
PubSub.publish 方法会将组件中的值发布出去,这些值将会告诉给那些订阅了这个信息的组件,一般是在事件触发时使用:
import PubSub from 'pubsub-js'
handleClick() {
// PubSub.publish(name, data)
// name: 定义的名称字符串,订阅是需要绑定
// data: 需要传输的数据
PubSub.publish('click', data)
}
订阅(subscribe)
PubSub.subscribe 方法会订阅发布组件中发布的数据,一般在刚进入组件的时候进行订阅:
import PubSub from 'pubsub-js'
componentDidMount() {
// PubSub.subscribe(name, callback)
// name: 订阅的名字,注意要和发布的名字一致
// callback: 回调函数,有两个参数:msg, data
// msg 是订阅的名字,data 是订阅的数据
PubSub.subscribe('click', (msg, data) => {
// 操作
})
}