RStudio shiny 개발자 컨퍼런스에서 shiny 응용프로그램 개발에 관련된 다양한 기술과 개발철학에 대한 본질적인 내용을 이해할 수 있다. Joe Cheng이 키노트로 나서 반응형 프로그래밍(reactive programming)에 대해 궁금한 점에 대해 많은 내용이 잘 기술되어 있다.
shiny 깨달음 사다리
shiny 학습서를 반정도 실습해서 input$, output$을 사용했다.shiny 학습서를 전부 실습했고, reactive() 반응형 표현식을 사용했다.observe(), observeEvent()를 사용했고, 반응형 표현식을 다른 상황에 따라 사용해봤다. isolate()도 적절히 사용했다.reactive() vs. observe() 를 확신을 가지고 사용할 때를 말할 수 있다. invalidateLater()도 사용해봤다.reactive() 함수를 작성했다. 고차 reactive() 함수는 입력 모수와 반환값을 갖는 반응형 표현식 함수다.reactive() 표현식이 모나드(monads)임을 이해한다.
만약 What is a Monad? - Computerphile 내용을 이미 알고 있다면 이미 상위 1%안에 드는 R 개발자입니다. 축하합니다!!!
reactive()와 observe() 비교반응(reactive)과 관측(observe)를 정확히 이해하는 것이 shiny 반응형 프로그램을 제작하는데 매우 중요하다. 왜냐하면, 나중에 크고 복잡한 shiny 웹앱 개발할 경우 발생하는 다양한 오류와 성능 등 다양한 품질문제가 이에 귀착되기 때문이다.
observe() + isolate()를 조합해서 사용했으나, 추후 observeEvent()로 일원화하면 코드가 훨씬 더 깔끔해진다.observeEvent() 함수 내부에 버튼(button)을 두고 버튼이 눌렸을 때 행동을 수행하는 코드가 위치해서 코드에 써진 로직을 수행한다.observeEvent(input$upload_button, {
httr::POST(server_url, jsonlite::toJSON(dataset()))
})reactive()와 observe()를 한단계 더 들어가서 비교해 보자. reactive()는 부작용(side effect) 없이 값을 계산하는 반면에 observe()는 부작용(side effect)를 갖는 행동(action)을 수반한다. 따라서, 실제로 코드가 실행되던지 말던 그다지 신경쓰지 않아도 되는 코드 블록에 reactive()를 사용하고, 값을 반환하지 않고 부작용(side effect)이 발생되는 경우 신경을 써야 되는 코드블록을 실행하는 경우 행위(action)이 수반되는 경우 observe()/observeEVent()를 사용한다.
lazy하고 캐쉬(cached)된다는 사실이 중요.
lazy 해서 일반 함수처럼 호출되기 전까지 코드를 실행하지 않는다.r1 <- function() { runif(1) }
r1()
# [1] 0.8403573
r1()
# [1] 0.4590713
r1()
# [1] 0.9816089return() 함수에 전달된 값, 최종 표현식의 값이 버려진다.eager 해서 뭔가 변경사항이 발생하면 즉시 실행된다.r2 <- reactive({ runif(1) })
r2() # [1] 0.5327107
r2() # [1] 0.5327107
r2() # [1] 0.5327107slide me 앱을 만들면서 shiny 모듈 개발에 대한 기본 개념과 방법을 숙지한다.
shiny 응용프로그램ui, server로 구성하고 shinyApp으로 실행하는 전형적인 shiny 앱은 다음과 같이 구성된다. 즉, sliderInput()에서 값을 조정하면 결과값이 textOutput()을 통해 출력되는 구조를 갖는다.
ui <- fluidPage(
sliderInput("slider", "Slide Me", 0, 100, 50),
textOutput("num")
)
server <- function(input, output) {
output$num <- renderText({
input$slider
})
}
shinyApp(ui, server)slider를 뺀 shiny 앱sliderInput()을 ㅎ마수로 빼서 slider() 함수로 작성해 본다. 역시 동일한 기능을 수행한 것으로 모듈화하는 전형적인 패턴의 시작이다.
slider <- function() {
sliderInput("slider", "Slide Me", 0, 100, 50)
}
ui <- fluidPage(
slider(),
textOutput("num")
)
server <- function(input, output) {
output$num <- renderText({
input$slider
})
}
shinyApp(ui, server)slider() 함수로 밖으로 빼서 slider() 기능을 갖는 shiny 앱을 만들 경우 문제에 봉착하게 된다. 즉, namespace가 그것인데 앱을 실행시켜 보면 쉽게 파악이 된다. sliderInput() 함수 슬라이더 명칭이 slider로 중복되어 textOutput()에 기대했던 값이 전달되지 않는 문제가 발생된다.
slider <- function() {
sliderInput("slider", "Slide Me", 0, 100, 50)
}
ui <- fluidPage(
slider(),
textOutput("num1"),
slider(),
textOutput("num2")
)
server <- function(input, output) {
output$num1 <- renderText({ input$slider })
output$num2 <- renderText({ input$slider })
}
shinyApp(ui, server)이에 대한 해법은 input$slider1, input$slider2와 같이 명칭을 달리 부여하면 문제를 해결할 수 있다. 하지만, 이와 같이 slider() 함수로 빼고 나서 일일이 명칭을 달리 부여하면 좋을까?
slider <- function(id) {
sliderInput(id, "Slide Me", 0, 100, 50)
}
ui <- fluidPage(
slider("slider1"),
textOutput("num1"),
slider("slider2"),
textOutput("num2")
)
server <- function(input, output) {
output$num1 <- renderText({ input$slider1 })
output$num2 <- renderText({ input$slider2 })
}
shinyApp(ui, server)shiny 모듈화 작업일반적인 함수가 아닌 shiny 모듈을 작성해보자. shiny 모듈도 함수의 일종인데 모듈 작성 규칙과 namespace를 결합하게 되면 shiny 모듈을 작성할 수 있게 된다.
shiny 모듈화 1단계shiny 모듈작성을 위해서 우선 틀을 잡는 단계가 첫걸음이 된다.
shiny 모듈은 모듈명이 있어야 하는데 sliderText를 모듈명을 정하게 되면 UI는 모듈명 + UI가 되고 서버 로직은 모듈명이 된다. 따라서, sliderTextUI가 UI가 되고, sliderText가 서버가 된다.
sliderTextUI <- function() {
#UI
}
sliderText <- function() {
# Server
}
slider <- function(id) {
sliderInput(id, "Slide Me", 0, 100, 50)
}
ui <- fluidPage(
slider("slider1"),
textOutput("num1"),
slider("slider2"),
textOutput("num2")
)
server <- function(input, output) {
output$num1 <- renderText({ input$slider1 })
output$num2 <- renderText({ input$slider2 })
}
shinyApp(ui, server)shiny 모듈화 2단계틀이 잡히게 되면 다음 단계로 모듈 UI를 작성하게 된다. 일반적으로 function(id)를 두어 모듈명을 전달하게 만든다. 그 다음으로 NS()를 통해 namesapce를 두어 모듈명이 겹치지 않게 만든다.
NS()를 도입하여 명칭이 중복되는 것을 방지하고 ns() 함수로 입력, 출력 명칭이 사용되는 곳을 감싼다.
그리고, tagList()로 UI 요소를 다수 정의할 수 있도록 정의하면 된다.
# Moudule UI
sliderTextUI <- function(id) {
ns <- NS(id)
tagList(
sliderInput(ns("slider"), "Slide Me", 0, 100, 50),
textOutput(ns("number"))
)
}
sliderText <- function() {
# Server
}
# ui.R
ui <- fluidPage(
sliderTextUI("one")
)
server <- function(input, output) {}
shinyApp(ui, server)shiny 모듈화 3단계모듈 UI를 작성하게 되면 상대적으로 server 작성은 단순하다. server.R에서 해당 UI에 상응하는 부분을 함수 내부에 가져오면 된다. 한가지 주의할 점은 인자로 input, output, session으로 됨에 유의한다.
server.R에서 callModule()로 해당 모듈을 호출하고 모듈명을 달리하면 동일한 응용프로그램에 다수 모듈을 구현하는 것도 힘들지 않고 가능하다.
# Moudule UI
sliderTextUI <- function(id) {
ns <- NS(id)
tagList(
sliderInput(ns("slider"), "Slide Me", 0, 100, 50),
textOutput(ns("number"))
)
}
sliderText <- function(input, output, session) {
output$number <- renderText({
input$slider
})
}
# ui.R -----
ui <- fluidPage(
sliderTextUI("one"),
sliderTextUI("two"),
sliderTextUI("three")
)
# server.R -----
server <- function(input, output) {
callModule(sliderText, "one")
callModule(sliderText, "two")
callModule(sliderText, "three")
}
shinyApp(ui, server)