宣告資料需求的主要方式是透過 createFragmentContainer
— 一個讓 React components 封裝它們的資料需要的 higher-order React component。
類似於 React component 的 render
方法不直接地調整原生的 view,Relay container 也不直接地抓取資料。container 宣告了 render 所需要的資料規範。Relay 來保證這份資料在 render 前可以使用。
一開始,讓我們來建置純 React 版的 <TodoItem>
component,它顯示 Todo
的文字和完成狀態。
這是個基本的 <TodoItem>
實作,它為了凸顯功能忽略了樣式:
class TodoItem extends React.Component { render() { // 預期 `item` prop 會是以下的格式: // { // item: { // text, // isComplete // } // } const item = this.props.item; return ( <View> <Checkbox checked={item.isComplete} /> <Text>{item.text}</Text> </View> ); } }
在 Relay 中,使用 GraphQL 來描述資料需求。以 <TodoItem>
來說,它的依賴關係可以被表達如同下面那樣。請記得,它必須完全符合 component 預期的 item
prop 的格式。
建議使用 <FileName>_<propName>
的命名慣例來命名 fragment。當從 classic 搬移到 modern API 需要這個限制來完成跨雙方的相容性。
graphql` # 這個 fragment 只能運用在 'Todo' 類別的物件上。 fragment TodoItem_item on Todo { text isComplete } `
給定一般的 React component 和一個 GraphQL fragment,我們就可以定義 Container
來告訴 Relay 這個 component 的資料需求。讓我們先來看一段程式碼接著看看發生什麼事:
class TodoItem extends React.Component {/* 同上 */} // Export 一個 *新的* React component,它包裝了原來的 `<TodoItem>`。 module.exports = createFragmentContainer(TodoItem, { // 針對所有倚賴伺服器資料的 props,我們在這個物件中 // 定義一個對應的 key。在這裡,這個 component 預期伺服器的資料會填入 // `item` prop,所以我們指定上面的 fragment 到 `item` key。 item: graphql` fragment TodoItem_item on Todo { text isComplete } `, });
上面的範例與 classic container API 非常相似,不過在 modern API 中我們可以直接傳遞 graphql
template literal 作為第二個參數。Relay 會從 fragment 名稱依照 fragment 命名慣例 <FileName>_<propName>
推斷出 prop 名稱。下面的範例跟上面的範例是相等的:
module.exports = createFragmentContainer( TodoItem, graphql` fragment TodoItem_item on Todo { text isComplete } `, );
如果沒有 _<propName>
前綴,將會使用 data
作為 prop 名稱:
class TodoItem extends React.Component { render() { const item = this.props.data; } } module.exports = createFragmentContainer( TodoItem, graphql` fragment TodoItem on Todo { text isComplete } `, );
React 和 Relay 支援透過組合建立任何複雜的應用程式。可以利用組合較小的 component 來創造較大 component,這能幫助我們建立模組化、強大的應用程式。Relay 組合了 component 的兩個方面:
讓我們藉由組合了上面的 <TodoItem>
component 的 <TodoList>
component 來探索這個運作原理。
View 組合就是你以往所做的 — Relay container 是標準的 React component。以下這是 <TodoList>
component:
class TodoList extends React.Component { render() { // 預期 `list` 有字串 `title`,以及 // 給 `<TodoItem>` 們的資訊 (我們會在接下來取得它)。 const list = this.props.list; return ( <View> {/* 它就像 React component 一般運作,因為它就是一個 React component! */} <Text>{list.title}</Text> {list.todoItems.map(item => <TodoItem item={item} />)} </View> ); } }
Fragment 組合的運作方式很類似 — parent container 的 fragment 組合了每一個 children 的 fragment。在這個範例中,<TodoList>
需要抓取 <TodoItem>
所需要的 Todo
資訊。
class TodoList extends React.Component {/* 同上 */} module.exports = createFragmentContainer( TodoList, // 這 `_list` fragment 命名後綴對應到 prop `list`, // 它預期會被 `<TodoList>` component 填入伺服器來的資料。 graphql` fragment TodoList_list on TodoList { # S指定任何 '<TodoList>' 自己需要的欄位。 title # 包含一個到 child component 的 fragment 的參考。 todoItems { ...TodoItem_item } } `, );
請記得在組合 fragment 時,被組合的 fragment 的類別必須符合在 parent 上嵌入它的欄位。例如,嵌入一個 Story
類型的 fragment 到 parent 裡面 User
類型的欄位中沒有意義。如果你做錯了,Relay 和 GraphQL 將會提供有用的錯誤訊息 (而如果它們不是非常有用,請讓我們知道!)。
React component classes may have methods, often accessed via refs.
Since Relay composes these component instances in a container, you need to use the componentRef
prop to access them:
Consider an input with a server-defined placeholder text and an imperative method to focus the input node:
module.exports = createFragmentContainer( class TodoInput extends React.Component { focus() { this.input.focus(); } render() { return <input ref={node => { this.input = node; }} placeholder={this.props.data.suggestedNextTitle} />; } }, graphql` fragment TodoInput on TodoList { suggestedNextTitle } `, );
To call this method on the underlying component, first provide a componentRef
function to the Relay container. This differs from providing a ref
function which would provide a reference to the Relay container itself, not the underlying React Component.
module.exports = createFragmentContainer( class TodoListView extends React.Component { render() { return <div onClick={() => this.input.focus()}> <TodoInput data={this.props.data} componentRef={ref => { this.input = ref; }} /> </div>; } }, graphql` fragment TodoListView on TodoList { ...TodoInput } `, );
我們前面已經學到,Relay fragment container 把資料需求宣告為 GraphQL fragment。
我們幾乎已經準備好讓 Relay 來滿足這些 component 的資料需求並 render 它們。不過,這還有一個問題。為了使用 GraphQL 實際地抓取資料,我們需要一個 query root。舉例來說,我們需要把 <TodoList>
fragment 包在一個 GraphQL query。
在 Relay 中,query 的 root 是透過 QueryRenderer 來定義,請查看那個章節以了解更多細節。